From 8a20416e492a8301c8bcdb264b9f6b1bd3d00dc6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:10:21 +0200 Subject: [PATCH 01/44] don't validate extension of dropped file --- .../ayon_core/tools/publisher/publish_report_viewer/window.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/window.py b/client/ayon_core/tools/publisher/publish_report_viewer/window.py index aedc3b9e31..d5742d73e0 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/window.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/window.py @@ -576,8 +576,7 @@ def dropEvent(self, event): filepaths = [] for url in mime_data.urls(): filepath = url.toLocalFile() - ext = os.path.splitext(filepath)[-1] - if os.path.exists(filepath) and ext == ".json": + if os.path.exists(filepath): filepaths.append(filepath) self._add_filepaths(filepaths) event.accept() From 3eb960a8568e5366ad685572c9107c653b8fb9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 8 Aug 2024 18:13:13 +0200 Subject: [PATCH 02/44] :art: use product name for render templates this is fixing issue where product name templates were hardcoded. Old behavior is enabled by default but deprecation warning is issued --- .../ayon_core/pipeline/create/product_name.py | 8 +- .../pipeline/farm/pyblish_functions.py | 276 +++++++++++++----- .../publish/collect_anatomy_instance_data.py | 17 +- client/ayon_core/plugins/publish/integrate.py | 5 + server/settings/tools.py | 8 + 5 files changed, 238 insertions(+), 76 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 8a08bdc36c..597c9f4862 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -1,7 +1,6 @@ import ayon_api - +from ayon_core.lib import StringTemplate, filter_profiles, prepare_template_data from ayon_core.settings import get_project_settings -from ayon_core.lib import filter_profiles, prepare_template_data from .constants import DEFAULT_PRODUCT_TEMPLATE @@ -183,7 +182,10 @@ def get_product_name( fill_pairs[key] = value try: - return template.format(**prepare_template_data(fill_pairs)) + return StringTemplate.format_template( + template=template, + data=prepare_template_data(fill_pairs) + ) except KeyError as exp: raise TemplateFillError( "Value for {} key is missing in template '{}'." diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 72deee185e..fc769a06bb 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -1,5 +1,5 @@ -import os import copy +import os import re import warnings from copy import deepcopy @@ -7,14 +7,11 @@ import attr import ayon_api import clique - -from ayon_core.pipeline import ( - get_current_project_name, - get_representation_path, -) from ayon_core.lib import Logger -from ayon_core.pipeline.publish import KnownPublishError +from ayon_core.pipeline import get_current_project_name, get_representation_path +from ayon_core.pipeline.create import get_product_name from ayon_core.pipeline.farm.patterning import match_aov_pattern +from ayon_core.pipeline.publish import KnownPublishError @attr.s @@ -250,6 +247,9 @@ def create_skeleton_instance( "colorspace": data.get("colorspace") } + if data.get("renderlayer"): + instance_skeleton_data["renderlayer"] = data["renderlayer"] + # skip locking version if we are creating v01 instance_version = data.get("version") # take this if exists if instance_version != 1: @@ -464,7 +464,9 @@ def create_instances_for_aov(instance, skeleton, aov_filter, Args: instance (pyblish.api.Instance): Original instance. skeleton (dict): Skeleton instance data. + aov_filter (dict): AOV filter. skip_integration_repre_list (list): skip + do_not_add_review (bool): Explicitly disable reviews Returns: list of pyblish.api.Instance: Instances created from @@ -515,6 +517,113 @@ def create_instances_for_aov(instance, skeleton, aov_filter, ) +def _get_legacy_product_name_and_group( + product_type, + source_product_name, + task_name, + dynamic_data): + """Get product name with legacy logic. + + This function holds legacy behaviour of creating product name + that is deprecated. This wasn't using product name templates + at all, only hardcoded values. It shouldn't be used anymore, + but transition to templates need careful checking of the project + and studio settings. + + Deprecated: + since 0.4.4 + + Args: + product_type (str): Product type. + source_product_name (str): Source product name. + task_name (str): Task name. + dynamic_data (dict): Dynamic data (camera, aov, ...) + + Returns: + tuple: product name and group name + + """ + warnings.warn("Using legacy product name for renders", + DeprecationWarning) + + if not source_product_name.startswith(product_type): + resulting_group_name = '{}{}{}{}{}'.format( + product_type, + task_name[0].upper(), task_name[1:], + source_product_name[0].upper(), source_product_name[1:]) + else: + resulting_group_name = source_product_name + + # create product name `` + if not source_product_name.startswith(product_type): + resulting_group_name = '{}{}{}{}{}'.format( + product_type, + task_name[0].upper(), task_name[1:], + source_product_name[0].upper(), source_product_name[1:]) + else: + resulting_group_name = source_product_name + + resulting_product_name = '{}'.format(resulting_group_name) + camera = dynamic_data.get("camera") + aov = dynamic_data.get("aov") + if camera: + if not aov: + resulting_product_name = '{}_{}'.format( + resulting_group_name, camera) + elif not aov.startswith(camera): + resulting_product_name = '{}_{}_{}'.format( + resulting_group_name, camera, aov) + else: + resulting_product_name = "{}_{}".format( + resulting_group_name, aov) + else: + if aov: + resulting_product_name = '{}_{}'.format( + resulting_group_name, aov) + + return resulting_product_name, resulting_group_name + + +def _get_product_name_and_group_from_template( + task_entity, + project_name, + host_name, + product_type, + variant, + project_settings, + dynamic_data=None): + """Get product name and group name from template. + + This will get product name and group name from template based on + data provided. It is doing similar work as + `func::_get_legacy_product_name_and_group` but using templates. + + Args: + task_entity (dict): Task entity. + project_name (str): Project name. + host_name (str): Host name. + product_type (str): Product type. + variant (str): Variant. + project_settings (dict): Project settings. + dynamic_data (dict): Dynamic data (aov, renderlayer, camera, ...). + + Returns: + tuple: product name and group name. + + """ + resulting_product_name = get_product_name( + project_name=project_name, + task_name=task_entity["taskName"], + task_type=task_entity["taskType"], + host_name=host_name, + product_type=product_type, + dynamic_data=dynamic_data, + variant=variant, + project_settings=project_settings, + ) + return resulting_product_name, "" + + def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, skip_integration_repre_list, do_not_add_review): """Create instance for each AOV found. @@ -526,10 +635,10 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, instance (pyblish.api.Instance): Original instance. skeleton (dict): Skeleton data for instance (those needed) later by collector. - additional_data (dict): .. + additional_data (dict): ... skip_integration_repre_list (list): list of extensions that shouldn't be published - do_not_addbe _review (bool): explicitly disable review + do_not_add_review (bool): explicitly disable review Returns: @@ -539,68 +648,69 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, ValueError: """ - # TODO: this needs to be taking the task from context or instance - task = os.environ["AYON_TASK_NAME"] anatomy = instance.context.data["anatomy"] - s_product_name = skeleton["productName"] + source_product_name = skeleton["productName"] cameras = instance.data.get("cameras", []) - exp_files = instance.data["expectedFiles"] + expected_files = instance.data["expectedFiles"] log = Logger.get_logger("farm_publishing") instances = [] # go through AOVs in expected files - for aov, files in exp_files[0].items(): - cols, rem = clique.assemble(files) - # we shouldn't have any reminders. And if we do, it should - # be just one item for single frame renders. - if not cols and rem: - if len(rem) != 1: - raise ValueError("Found multiple non related files " - "to render, don't know what to do " - "with them.") - col = rem[0] - ext = os.path.splitext(col)[1].lstrip(".") - else: - # but we really expect only one collection. - # Nothing else make sense. - if len(cols) != 1: - raise ValueError("Only one image sequence type is expected.") # noqa: E501 - ext = cols[0].tail.lstrip(".") - col = list(cols[0]) + for aov, files in expected_files[0].items(): + collected_files = _collect_expected_files_for_aov(files) - # create product name `` - # TODO refactor/remove me - product_type = skeleton["productType"] - if not s_product_name.startswith(product_type): - group_name = '{}{}{}{}{}'.format( - product_type, - task[0].upper(), task[1:], - s_product_name[0].upper(), s_product_name[1:]) - else: - group_name = s_product_name - - # if there are multiple cameras, we need to add camera name - expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cams = [cam for cam in cameras if cam in expected_filepath] - if cams: - for cam in cams: - if not aov: - product_name = '{}_{}'.format(group_name, cam) - elif not aov.startswith(cam): - product_name = '{}_{}_{}'.format(group_name, cam, aov) - else: - product_name = "{}_{}".format(group_name, aov) - else: - if aov: - product_name = '{}_{}'.format(group_name, aov) - else: - product_name = '{}'.format(group_name) + expected_filepath = collected_files[0] \ + if isinstance(collected_files, (list, tuple)) else collected_files + + dynamic_data = { + "aov": aov, + "renderlayer": instance.data.get("renderlayer"), + } + + # find if camera is used in the file path + # TODO: this must be changed to be more robust. Any coincidence + # of camera name in the file path will be considered as + # camera name. This is not correct. + camera = [cam for cam in cameras if cam in expected_filepath] + + # Is there just one camera matching? + # TODO: this is not true, we can have multiple cameras in the scene + # and we should be able to detect them all. Currently, we are + # keeping the old behavior, taking the first one found. + if camera: + dynamic_data["camera"] = camera[0] + + project_settings = instance.context.data.get("project_settings") + + use_legacy_product_name = True + try: + use_legacy_product_name = project_settings["core"]["tools"]["creator"]["product_name_profiles"]["use_legacy_for_renders"] # noqa: E501 + except KeyError: + warnings.warn( + ("use_legacy_for_renders not found in project settings. " + "Using legacy product name for renders. Please update " + "your ayon-core version."), DeprecationWarning) + use_legacy_product_name = True + + if use_legacy_product_name: + product_name, group_name = _get_legacy_product_name_and_group( + product_type=skeleton["productType"], + source_product_name=source_product_name, + task_name=instance.data["task"], + dynamic_data=dynamic_data) - if isinstance(col, (list, tuple)): - staging = os.path.dirname(col[0]) else: - staging = os.path.dirname(col) + product_name, group_name = _get_product_name_and_group_from_template( + task_entity=instance.data["taskEntity"], + project_name=instance.context.data["projectName"], + host_name=instance.context.data["hostName"], + product_type=skeleton["productType"], + variant=instance.data.get('variant', ''), + project_settings=project_settings + ) + + staging = os.path.dirname(expected_filepath) try: staging = remap_source(staging, anatomy) @@ -611,10 +721,8 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, app = os.environ.get("AYON_HOST_NAME", "") - if isinstance(col, list): - render_file_name = os.path.basename(col[0]) - else: - render_file_name = os.path.basename(col) + render_file_name = os.path.basename(expected_filepath) + aov_patterns = aov_filter preview = match_aov_pattern(app, aov_patterns, render_file_name) @@ -622,9 +730,10 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, new_instance = deepcopy(skeleton) new_instance["productName"] = product_name new_instance["productGroup"] = group_name + new_instance["aov"] = aov # toggle preview on if multipart is on - # Because we cant query the multipartExr data member of each AOV we'll + # Because we can't query the multipartExr data member of each AOV we'll # need to have hardcoded rule of excluding any renders with # "cryptomatte" in the file name from being a multipart EXR. This issue # happens with Redshift that forces Cryptomatte renders to be separate @@ -650,10 +759,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, new_instance["review"] = True # create representation - if isinstance(col, (list, tuple)): - files = [os.path.basename(f) for f in col] - else: - files = os.path.basename(col) + ext = os.path.splitext(render_file_name)[-1].lstrip(".") # Copy render product "colorspace" data to representation. colorspace = "" @@ -708,6 +814,36 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, return instances +def _collect_expected_files_for_aov(files): + """Collect expected files. + + Args: + files (list): List of files. + + Returns: + list or str: Collection of files or single file. + + Raises: + ValueError: If there are multiple collections. + + """ + cols, rem = clique.assemble(files) + # we shouldn't have any reminders. And if we do, it should + # be just one item for single frame renders. + if not cols and rem: + if len(rem) != 1: + raise ValueError("Found multiple non related files " + "to render, don't know what to do " + "with them.") + return rem[0] + else: + # but we really expect only one collection. + # Nothing else make sense. + if len(cols) != 1: + raise ValueError("Only one image sequence type is expected.") # noqa: E501 + return list(cols[0]) + + def get_resources(project_name, version_entity, extension=None): """Get the files from the specific version. diff --git a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py index b6636696c1..c83906d7f1 100644 --- a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py +++ b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py @@ -138,7 +138,7 @@ def fill_missing_task_entities(self, context, project_name): folder_path_by_id = {} for instance in context: folder_entity = instance.data.get("folderEntity") - # Skip if instnace does not have filled folder entity + # Skip if instance does not have filled folder entity if not folder_entity: continue folder_id = folder_entity["id"] @@ -385,8 +385,19 @@ def fill_anatomy_data(self, context): json.dumps(anatomy_data, indent=4) )) + # make render layer available in anatomy data + render_layer = instance.data.get("renderLayer") + if render_layer: + anatomy_data["renderLayer"] = render_layer + + # make aov name available in anatomy data + aov = instance.data.get("aov") + if aov: + anatomy_data["aov"] = aov + + def _fill_folder_data(self, instance, project_entity, anatomy_data): - # QUESTION should we make sure that all folder data are poped if + # QUESTION: should we make sure that all folder data are popped if # folder data cannot be found? # - 'folder', 'hierarchy', 'parent', 'folder' folder_entity = instance.data.get("folderEntity") @@ -426,7 +437,7 @@ def _fill_folder_data(self, instance, project_entity, anatomy_data): }) def _fill_task_data(self, instance, task_types_by_name, anatomy_data): - # QUESTION should we make sure that all task data are poped if task + # QUESTION: should we make sure that all task data are popped if task # data cannot be resolved? # - 'task' diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index 69c14465eb..d3f6c04333 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -744,6 +744,11 @@ def prepare_representation( if not is_udim: repre_context["frame"] = first_index_padded + # store renderlayer in context if it exists + # to be later used for example by delivery templates + if instance.data.get("renderlayer"): + repre_context["renderlayer"] = instance.data["renderlayer"] + # Update the destination indexes and padding dst_collection = clique.assemble(dst_filepaths)[0][0] dst_collection.padding = destination_padding diff --git a/server/settings/tools.py b/server/settings/tools.py index 85a66f6a70..3b2c140b3a 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -22,6 +22,14 @@ def normalize_value(cls, value): class ProductNameProfile(BaseSettingsModel): _layout = "expanded" + # TODO: change to False in next releases + use_legacy_for_renders: bool = SettingsField( + True, title="Use legacy for renders", + description="Use product naming logic for renders. " + "This is for backwards compatibility enabled by default." + "When enabled, it will ignore any templates for renders " + "that are set in the product name profiles.") + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) From 551d4f2b7065c3c13467aff3ab749e7a9632dc8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 8 Aug 2024 18:13:47 +0200 Subject: [PATCH 03/44] :fire: remove old python interface file --- .../pipeline/farm/pyblish_functions.pyi | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 client/ayon_core/pipeline/farm/pyblish_functions.pyi diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.pyi b/client/ayon_core/pipeline/farm/pyblish_functions.pyi deleted file mode 100644 index fe0ae57da0..0000000000 --- a/client/ayon_core/pipeline/farm/pyblish_functions.pyi +++ /dev/null @@ -1,24 +0,0 @@ -import pyblish.api -from ayon_core.pipeline import Anatomy -from typing import Tuple, List - - -class TimeData: - start: int - end: int - fps: float | int - step: int - handle_start: int - handle_end: int - - def __init__(self, start: int, end: int, fps: float | int, step: int, handle_start: int, handle_end: int): - ... - ... - -def remap_source(source: str, anatomy: Anatomy): ... -def extend_frames(folder_path: str, product_name: str, start: int, end: int) -> Tuple[int, int]: ... -def get_time_data_from_instance_or_context(instance: pyblish.api.Instance) -> TimeData: ... -def get_transferable_representations(instance: pyblish.api.Instance) -> list: ... -def create_skeleton_instance(instance: pyblish.api.Instance, families_transfer: list = ..., instance_transfer: dict = ...) -> dict: ... -def create_instances_for_aov(instance: pyblish.api.Instance, skeleton: dict, aov_filter: dict) -> List[pyblish.api.Instance]: ... -def attach_instances_to_product(attach_to: list, instances: list) -> list: ... From 6608a18bb0c8e83acd43eb577d31357a694f8a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 8 Aug 2024 18:38:56 +0200 Subject: [PATCH 04/44] :recycle: make function public --- client/ayon_core/pipeline/farm/pyblish_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index fc769a06bb..4825818980 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -584,7 +584,7 @@ def _get_legacy_product_name_and_group( return resulting_product_name, resulting_group_name -def _get_product_name_and_group_from_template( +def get_product_name_and_group_from_template( task_entity, project_name, host_name, @@ -701,7 +701,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, dynamic_data=dynamic_data) else: - product_name, group_name = _get_product_name_and_group_from_template( + product_name, group_name = get_product_name_and_group_from_template( task_entity=instance.data["taskEntity"], project_name=instance.context.data["projectName"], host_name=instance.context.data["hostName"], From 6acff9e04653930eeb51d83d20af28b8c5746460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 8 Aug 2024 19:00:33 +0200 Subject: [PATCH 05/44] :recycle: first shot at group name also removing need for project settings --- client/ayon_core/pipeline/farm/pyblish_functions.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 4825818980..5a00f8e973 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -590,7 +590,6 @@ def get_product_name_and_group_from_template( host_name, product_type, variant, - project_settings, dynamic_data=None): """Get product name and group name from template. @@ -604,7 +603,6 @@ def get_product_name_and_group_from_template( host_name (str): Host name. product_type (str): Product type. variant (str): Variant. - project_settings (dict): Project settings. dynamic_data (dict): Dynamic data (aov, renderlayer, camera, ...). Returns: @@ -619,9 +617,8 @@ def get_product_name_and_group_from_template( product_type=product_type, dynamic_data=dynamic_data, variant=variant, - project_settings=project_settings, ) - return resulting_product_name, "" + return resulting_product_name, f"{product_type}{variant}" def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, @@ -706,8 +703,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, project_name=instance.context.data["projectName"], host_name=instance.context.data["hostName"], product_type=skeleton["productType"], - variant=instance.data.get('variant', ''), - project_settings=project_settings + variant=instance.data.get('variant', source_product_name), ) staging = os.path.dirname(expected_filepath) From 07f697d5a4dea4618fc480cdbe76f37a96ed5396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 9 Aug 2024 10:43:05 +0200 Subject: [PATCH 06/44] :recycle: derive group name from templates too --- .../pipeline/farm/pyblish_functions.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 5a00f8e973..0cc3cef879 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -597,6 +597,13 @@ def get_product_name_and_group_from_template( data provided. It is doing similar work as `func::_get_legacy_product_name_and_group` but using templates. + To get group name, template is called without any dynamic data, so + (depending on the template itself) it should be product name without + aov. + + Todo: + Maybe we should introduce templates for the groups themselves. + Args: task_entity (dict): Task entity. project_name (str): Project name. @@ -609,6 +616,16 @@ def get_product_name_and_group_from_template( tuple: product name and group name. """ + + resulting_group_name = get_product_name( + project_name=project_name, + task_name=task_entity["taskName"], + task_type=task_entity["taskType"], + host_name=host_name, + product_type=product_type, + variant=variant, + ) + resulting_product_name = get_product_name( project_name=project_name, task_name=task_entity["taskName"], @@ -618,7 +635,7 @@ def get_product_name_and_group_from_template( dynamic_data=dynamic_data, variant=variant, ) - return resulting_product_name, f"{product_type}{variant}" + return resulting_product_name, resulting_group_name def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, From a394556103dfd6f300e0f1998286fb1d0dfc4f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 9 Aug 2024 13:17:20 +0200 Subject: [PATCH 07/44] :bug: fix task name reference --- client/ayon_core/pipeline/farm/pyblish_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 0cc3cef879..54ee081c9f 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -619,7 +619,7 @@ def get_product_name_and_group_from_template( resulting_group_name = get_product_name( project_name=project_name, - task_name=task_entity["taskName"], + task_name=task_entity["name"], task_type=task_entity["taskType"], host_name=host_name, product_type=product_type, @@ -628,7 +628,7 @@ def get_product_name_and_group_from_template( resulting_product_name = get_product_name( project_name=project_name, - task_name=task_entity["taskName"], + task_name=task_entity["name"], task_type=task_entity["taskType"], host_name=host_name, product_type=product_type, From c63d2005cf5cad0941b0097c2c5c00e816ba3f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 9 Aug 2024 13:18:16 +0200 Subject: [PATCH 08/44] :recycle: move the setting one level up --- .../ayon_core/pipeline/farm/pyblish_functions.py | 2 +- server/settings/tools.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 54ee081c9f..aa30aefbd0 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -699,7 +699,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, use_legacy_product_name = True try: - use_legacy_product_name = project_settings["core"]["tools"]["creator"]["product_name_profiles"]["use_legacy_for_renders"] # noqa: E501 + use_legacy_product_name = project_settings["core"]["tools"]["creator"]["use_legacy_product_names_for_renders"] # noqa: E501 except KeyError: warnings.warn( ("use_legacy_for_renders not found in project settings. " diff --git a/server/settings/tools.py b/server/settings/tools.py index 3b2c140b3a..5e9c8e14a0 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -22,13 +22,6 @@ def normalize_value(cls, value): class ProductNameProfile(BaseSettingsModel): _layout = "expanded" - # TODO: change to False in next releases - use_legacy_for_renders: bool = SettingsField( - True, title="Use legacy for renders", - description="Use product naming logic for renders. " - "This is for backwards compatibility enabled by default." - "When enabled, it will ignore any templates for renders " - "that are set in the product name profiles.") product_types: list[str] = SettingsField( default_factory=list, title="Product types" @@ -73,6 +66,14 @@ class CreatorToolModel(BaseSettingsModel): title="Create Smart Select" ) ) + # TODO: change to False in next releases + use_legacy_product_names_for_renders: bool = SettingsField( + True, title="Use legacy product names for renders", + description="Use product naming templates for renders. " + "This is for backwards compatibility enabled by default." + "When enabled, it will ignore any templates for renders " + "that are set in the product name profiles.") + product_name_profiles: list[ProductNameProfile] = SettingsField( default_factory=list, title="Product name profiles" From 3350b91cf934aceb0558423e5b7707fd50934315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 9 Aug 2024 13:27:34 +0200 Subject: [PATCH 09/44] :bug: fix letter-case typo --- .../plugins/publish/collect_anatomy_instance_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py index c83906d7f1..5b750a5232 100644 --- a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py +++ b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py @@ -386,9 +386,9 @@ def fill_anatomy_data(self, context): )) # make render layer available in anatomy data - render_layer = instance.data.get("renderLayer") + render_layer = instance.data.get("renderlayer") if render_layer: - anatomy_data["renderLayer"] = render_layer + anatomy_data["renderlayer"] = render_layer # make aov name available in anatomy data aov = instance.data.get("aov") From eddba580ef29cbe1525a4c1579571afb925bb400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 9 Aug 2024 14:17:20 +0200 Subject: [PATCH 10/44] :recycle: explicitly remove `aov` key for group name this solution is a little hack. proper one would be probably introducing product group name templates as mentioned in the code comment --- client/ayon_core/pipeline/farm/pyblish_functions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index aa30aefbd0..91e155469d 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -616,13 +616,18 @@ def get_product_name_and_group_from_template( tuple: product name and group name. """ - + # remove 'aov' from data used to format group. See todo comment above + # for possible solution. + _dynamic_data = deepcopy(dynamic_data) or {} + if _dynamic_data["aov"]: + del _dynamic_data["aov"] resulting_group_name = get_product_name( project_name=project_name, task_name=task_entity["name"], task_type=task_entity["taskType"], host_name=host_name, product_type=product_type, + dynamic_data=_dynamic_data, variant=variant, ) @@ -721,6 +726,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, host_name=instance.context.data["hostName"], product_type=skeleton["productType"], variant=instance.data.get('variant', source_product_name), + dynamic_data=dynamic_data ) staging = os.path.dirname(expected_filepath) From 9ff4ec856b0ce9f78fe15bedabbca731b36fd837 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:00:10 +0200 Subject: [PATCH 11/44] introduced new function to get launcher storage and local dir --- client/ayon_core/lib/__init__.py | 4 +++ client/ayon_core/lib/local_settings.py | 49 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index 12c391d867..d4b161031e 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -9,6 +9,8 @@ AYONSettingsRegistry, OpenPypeSecureRegistry, OpenPypeSettingsRegistry, + get_launcher_local_dir, + get_launcher_storage_dir, get_local_site_id, get_ayon_username, get_openpype_username, @@ -144,6 +146,8 @@ "AYONSettingsRegistry", "OpenPypeSecureRegistry", "OpenPypeSettingsRegistry", + "get_launcher_local_dir", + "get_launcher_storage_dir", "get_local_site_id", "get_ayon_username", "get_openpype_username", diff --git a/client/ayon_core/lib/local_settings.py b/client/ayon_core/lib/local_settings.py index 54432265d9..68ec48695f 100644 --- a/client/ayon_core/lib/local_settings.py +++ b/client/ayon_core/lib/local_settings.py @@ -30,6 +30,55 @@ def wrapper(*args, **kwargs): _PLACEHOLDER = object() +def get_launcher_storage_dir(*subdirs: str) -> str: + """Get storage directory for launcher. + + Storage directory is used for storing shims, addons, dependencies, etc. + + It is not recommended, but the location can be shared across + multiple machines. + + Note: + This function should be called at least once on bootstrap. + + Args: + *subdirs (str): Subdirectories relative to storage dir. + + Returns: + str: Path to storage directory. + + """ + storage_dir = os.getenv("AYON_LAUNCHER_STORAGE_DIR") + if not storage_dir: + storage_dir = get_ayon_appdirs() + + return os.path.join(storage_dir, *subdirs) + + +def get_launcher_local_dir(*subdirs: str) -> str: + """Get local directory for launcher. + + Local directory is used for storing machine or user specific data. + + The location is user specific. + + Note: + This function should be called at least once on bootstrap. + + Args: + *subdirs (str): Subdirectories relative to local dir. + + Returns: + str: Path to local directory. + + """ + storage_dir = os.getenv("AYON_LAUNCHER_LOCAL_DIR") + if not storage_dir: + storage_dir = get_ayon_appdirs() + + return os.path.join(storage_dir, *subdirs) + + class AYONSecureRegistry: """Store information using keyring. From 0e95019995d6d5b077a9487692934a2ac5afe34d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:03:17 +0200 Subject: [PATCH 12/44] use new functions in codebase --- client/ayon_core/addon/base.py | 11 ++++++----- client/ayon_core/lib/local_settings.py | 10 ++-------- client/ayon_core/pipeline/thumbnails.py | 4 ++-- .../tools/publisher/publish_report_viewer/window.py | 10 +++------- client/ayon_core/tools/tray/lib.py | 4 ++-- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 001ec5d534..0dbbe19942 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -17,7 +17,11 @@ from semver import VersionInfo from ayon_core import AYON_CORE_ROOT -from ayon_core.lib import Logger, is_dev_mode_enabled +from ayon_core.lib import ( + Logger, + is_dev_mode_enabled, + get_launcher_storage_dir, +) from ayon_core.settings import get_studio_settings from .interfaces import ( @@ -327,10 +331,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log): addons_dir = os.environ.get("AYON_ADDONS_DIR") if not addons_dir: - addons_dir = os.path.join( - appdirs.user_data_dir("AYON", "Ynput"), - "addons" - ) + addons_dir = get_launcher_storage_dir("addons") dev_mode_enabled = is_dev_mode_enabled() dev_addons_info = {} diff --git a/client/ayon_core/lib/local_settings.py b/client/ayon_core/lib/local_settings.py index 68ec48695f..a4ffdb8e39 100644 --- a/client/ayon_core/lib/local_settings.py +++ b/client/ayon_core/lib/local_settings.py @@ -519,20 +519,14 @@ def _delete_item(self, name): class AYONSettingsRegistry(JSONSettingRegistry): """Class handling AYON general settings registry. - Attributes: - vendor (str): Name used for path construction. - product (str): Additional name used for path construction. - Args: name (Optional[str]): Name of the registry. """ def __init__(self, name=None): - self.vendor = "Ynput" - self.product = "AYON" if not name: name = "AYON_settings" - path = appdirs.user_data_dir(self.product, self.vendor) + path = get_launcher_storage_dir() super(AYONSettingsRegistry, self).__init__(name, path) @@ -578,7 +572,7 @@ def get_local_site_id(): if site_id: return site_id - site_id_path = get_ayon_appdirs("site_id") + site_id_path = get_launcher_local_dir("site_id") if os.path.exists(site_id_path): with open(site_id_path, "r") as stream: site_id = stream.read() diff --git a/client/ayon_core/pipeline/thumbnails.py b/client/ayon_core/pipeline/thumbnails.py index dbb38615d8..401d95f273 100644 --- a/client/ayon_core/pipeline/thumbnails.py +++ b/client/ayon_core/pipeline/thumbnails.py @@ -4,7 +4,7 @@ import ayon_api -from ayon_core.lib.local_settings import get_ayon_appdirs +from ayon_core.lib.local_settings import get_launcher_local_dir FileInfo = collections.namedtuple( @@ -54,7 +54,7 @@ def get_thumbnails_dir(self): """ if self._thumbnails_dir is None: - self._thumbnails_dir = get_ayon_appdirs("thumbnails") + self._thumbnails_dir = get_launcher_local_dir("thumbnails") return self._thumbnails_dir thumbnails_dir = property(get_thumbnails_dir) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/window.py b/client/ayon_core/tools/publisher/publish_report_viewer/window.py index aedc3b9e31..9d1ed86a2b 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/window.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/window.py @@ -2,11 +2,11 @@ import json import uuid -import appdirs import arrow from qtpy import QtWidgets, QtCore, QtGui from ayon_core import style +from ayon_core.lib import get_launcher_local_dir from ayon_core.resources import get_ayon_icon_filepath from ayon_core.tools import resources from ayon_core.tools.utils import ( @@ -35,12 +35,8 @@ def get_reports_dir(): str: Path to directory where reports are stored. """ - report_dir = os.path.join( - appdirs.user_data_dir("AYON", "Ynput"), - "publish_report_viewer" - ) - if not os.path.exists(report_dir): - os.makedirs(report_dir) + report_dir = get_launcher_local_dir("publish_report_viewer") + os.makedirs(report_dir, exist_ok=True) return report_dir diff --git a/client/ayon_core/tools/tray/lib.py b/client/ayon_core/tools/tray/lib.py index fd84a9bd10..5f92e8a04f 100644 --- a/client/ayon_core/tools/tray/lib.py +++ b/client/ayon_core/tools/tray/lib.py @@ -19,7 +19,7 @@ run_detached_process, get_ayon_username, ) -from ayon_core.lib.local_settings import get_ayon_appdirs +from ayon_core.lib.local_settings import get_launcher_local_dir class TrayState: @@ -146,7 +146,7 @@ def get_tray_storage_dir() -> str: str: Tray storage directory where metadata files are stored. """ - return get_ayon_appdirs("tray") + return get_launcher_local_dir("tray") def _get_tray_info_filepath( From 9d2ff9637fb67ba2e1af479caee9b41d80712d73 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:05:20 +0200 Subject: [PATCH 13/44] marked 'get_ayon_appdirs' as deprecated --- client/ayon_core/lib/local_settings.py | 53 +++++++++++++++++--------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/client/ayon_core/lib/local_settings.py b/client/ayon_core/lib/local_settings.py index a4ffdb8e39..a6814f8b2f 100644 --- a/client/ayon_core/lib/local_settings.py +++ b/client/ayon_core/lib/local_settings.py @@ -3,6 +3,7 @@ import os import json import platform +import warnings from datetime import datetime from abc import ABC, abstractmethod @@ -30,6 +31,38 @@ def wrapper(*args, **kwargs): _PLACEHOLDER = object() +def _get_ayon_appdirs(*args): + return os.path.join( + appdirs.user_data_dir("AYON", "Ynput"), + *args + ) + + +def get_ayon_appdirs(*args): + """Local app data directory of AYON client. + + Deprecated: + Use 'get_launcher_local_dir' or 'get_launcher_storage_dir' based on + use-case. Deprecation added 24/08/09 (0.4.4-dev.1). + + Args: + *args (Iterable[str]): Subdirectories/files in local app data dir. + + Returns: + str: Path to directory/file in local app data dir. + + """ + warnings.warn( + ( + "Function 'get_ayon_appdirs' is deprecated. Should be replaced" + " with 'get_launcher_local_dir' or 'get_launcher_storage_dir'" + " based on use-case." + ), + DeprecationWarning + ) + return _get_ayon_appdirs(*args) + + def get_launcher_storage_dir(*subdirs: str) -> str: """Get storage directory for launcher. @@ -50,7 +83,7 @@ def get_launcher_storage_dir(*subdirs: str) -> str: """ storage_dir = os.getenv("AYON_LAUNCHER_STORAGE_DIR") if not storage_dir: - storage_dir = get_ayon_appdirs() + storage_dir = _get_ayon_appdirs() return os.path.join(storage_dir, *subdirs) @@ -74,7 +107,7 @@ def get_launcher_local_dir(*subdirs: str) -> str: """ storage_dir = os.getenv("AYON_LAUNCHER_LOCAL_DIR") if not storage_dir: - storage_dir = get_ayon_appdirs() + storage_dir = _get_ayon_appdirs() return os.path.join(storage_dir, *subdirs) @@ -546,22 +579,6 @@ def _create_local_site_id(registry=None): return new_id -def get_ayon_appdirs(*args): - """Local app data directory of AYON client. - - Args: - *args (Iterable[str]): Subdirectories/files in local app data dir. - - Returns: - str: Path to directory/file in local app data dir. - """ - - return os.path.join( - appdirs.user_data_dir("AYON", "Ynput"), - *args - ) - - def get_local_site_id(): """Get local site identifier. From f3c9ffac1a5fde32cabe419d12025bb0c1e7165d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:05:32 +0200 Subject: [PATCH 14/44] removed unused function '_create_local_site_id' --- client/ayon_core/lib/local_settings.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/client/ayon_core/lib/local_settings.py b/client/ayon_core/lib/local_settings.py index a6814f8b2f..256e7bcd28 100644 --- a/client/ayon_core/lib/local_settings.py +++ b/client/ayon_core/lib/local_settings.py @@ -563,22 +563,6 @@ def __init__(self, name=None): super(AYONSettingsRegistry, self).__init__(name, path) -def _create_local_site_id(registry=None): - """Create a local site identifier.""" - from coolname import generate_slug - - if registry is None: - registry = AYONSettingsRegistry() - - new_id = generate_slug(3) - - print("Created local site id \"{}\"".format(new_id)) - - registry.set_item("localId", new_id) - - return new_id - - def get_local_site_id(): """Get local site identifier. From c9a2022d0c87227be96123a3074fedbfd8614baa Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:19:27 +0200 Subject: [PATCH 15/44] remove unused import --- client/ayon_core/addon/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 0dbbe19942..383703e2bc 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -12,7 +12,6 @@ from abc import ABC, abstractmethod from typing import Optional -import appdirs import ayon_api from semver import VersionInfo From eaa1b503b7e3e956ed753d83644dad8023765d24 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 14 Aug 2024 17:52:29 -0400 Subject: [PATCH 16/44] Validate opening and closing brackets on Anatomy keys. --- client/ayon_core/lib/path_templates.py | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/client/ayon_core/lib/path_templates.py b/client/ayon_core/lib/path_templates.py index 01a6985a25..0a39d05e1d 100644 --- a/client/ayon_core/lib/path_templates.py +++ b/client/ayon_core/lib/path_templates.py @@ -460,6 +460,30 @@ def validate_value_type(value): return True return False + @staticmethod + def validate_key_is_matched(key): + """ + Finds out how balanced an expression is. + With a string containing only brackets. + + >>> is_matched('[]()()(((([])))') + False + >>> is_matched('[](){{{[]}}}') + True + """ + opening = tuple('({[') + closing = tuple(')}]') + mapping = dict(zip(opening, closing)) + queue = [] + + for letter in key: + if letter in opening: + queue.append(mapping[letter]) + elif letter in closing: + if not queue or letter != queue.pop(): + return False + return not queue + def format(self, data, result): """Format the formattings string. @@ -472,6 +496,12 @@ def format(self, data, result): result.add_output(result.realy_used_values[key]) return result + # ensure key is properly formed [({})] properly closed. + if not self.validate_key_is_matched(key): + result.add_missing_key(key) + result.add_output(self.template) + return result + # check if key expects subdictionary keys (e.g. project[name]) existence_check = key key_padding = list(KEY_PADDING_PATTERN.findall(existence_check)) From 44c417fdb0e960c6f8af2bae56309efd70724dc6 Mon Sep 17 00:00:00 2001 From: __robin__ Date: Mon, 19 Aug 2024 08:08:33 -0400 Subject: [PATCH 17/44] Update client/ayon_core/lib/path_templates.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/path_templates.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/lib/path_templates.py b/client/ayon_core/lib/path_templates.py index 0a39d05e1d..46dd997da2 100644 --- a/client/ayon_core/lib/path_templates.py +++ b/client/ayon_core/lib/path_templates.py @@ -462,14 +462,17 @@ def validate_value_type(value): @staticmethod def validate_key_is_matched(key): - """ - Finds out how balanced an expression is. - With a string containing only brackets. + """Validate that opening has closing at correct place. + + Example: + >>> is_matched("[]()()(((([])))") + False + >>> is_matched("[](){{{[]}}}") + True + + Returns: + bool: Openings and closinga are valid. - >>> is_matched('[]()()(((([])))') - False - >>> is_matched('[](){{{[]}}}') - True """ opening = tuple('({[') closing = tuple(')}]') From 4d884db269de1f8de4b5824bfa9fb5a7b728220f Mon Sep 17 00:00:00 2001 From: __robin__ Date: Mon, 19 Aug 2024 08:08:50 -0400 Subject: [PATCH 18/44] Update client/ayon_core/lib/path_templates.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/path_templates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/lib/path_templates.py b/client/ayon_core/lib/path_templates.py index 46dd997da2..edc7478cef 100644 --- a/client/ayon_core/lib/path_templates.py +++ b/client/ayon_core/lib/path_templates.py @@ -474,8 +474,8 @@ def validate_key_is_matched(key): bool: Openings and closinga are valid. """ - opening = tuple('({[') - closing = tuple(')}]') + opening = tuple("({[") + closing = tuple(")}]") mapping = dict(zip(opening, closing)) queue = [] From b0490fd15dbd24534c15e1f80ccf116b5ef2003f Mon Sep 17 00:00:00 2001 From: robin Date: Mon, 19 Aug 2024 08:13:55 -0400 Subject: [PATCH 19/44] Adjust docstring from PR feedback. --- client/ayon_core/lib/path_templates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/lib/path_templates.py b/client/ayon_core/lib/path_templates.py index edc7478cef..3e3bdd8f78 100644 --- a/client/ayon_core/lib/path_templates.py +++ b/client/ayon_core/lib/path_templates.py @@ -463,6 +463,7 @@ def validate_value_type(value): @staticmethod def validate_key_is_matched(key): """Validate that opening has closing at correct place. + Future-proof, only square brackets are currently used in keys. Example: >>> is_matched("[]()()(((([])))") @@ -471,7 +472,7 @@ def validate_key_is_matched(key): True Returns: - bool: Openings and closinga are valid. + bool: Openings and closing are valid. """ opening = tuple("({[") From f0cfc968d83294fe8d0a0c93dfca17c61a71c41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:01:22 +0200 Subject: [PATCH 20/44] Update client/ayon_core/pipeline/create/product_name.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/create/product_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 597c9f4862..3ca6611644 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -182,7 +182,7 @@ def get_product_name( fill_pairs[key] = value try: - return StringTemplate.format_template( + return StringTemplate.format_strict_template( template=template, data=prepare_template_data(fill_pairs) ) From 2e79075fe35430d754e01d98496fc708960bb11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:17:12 +0200 Subject: [PATCH 21/44] Update server/settings/tools.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- server/settings/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/settings/tools.py b/server/settings/tools.py index 5e9c8e14a0..a2785c1edf 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -68,7 +68,8 @@ class CreatorToolModel(BaseSettingsModel): ) # TODO: change to False in next releases use_legacy_product_names_for_renders: bool = SettingsField( - True, title="Use legacy product names for renders", + True, + title="Use legacy product names for renders", description="Use product naming templates for renders. " "This is for backwards compatibility enabled by default." "When enabled, it will ignore any templates for renders " From 1f45f8e0a07f91081defcf2e74693d8a005c5fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:17:29 +0200 Subject: [PATCH 22/44] Update client/ayon_core/pipeline/farm/pyblish_functions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/farm/pyblish_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 91e155469d..fc9371f719 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -563,7 +563,7 @@ def _get_legacy_product_name_and_group( else: resulting_group_name = source_product_name - resulting_product_name = '{}'.format(resulting_group_name) + resulting_product_name = resulting_group_name camera = dynamic_data.get("camera") aov = dynamic_data.get("aov") if camera: From 51c51eeef0415fc65b52d1bc35949926cd996526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:18:29 +0200 Subject: [PATCH 23/44] Update client/ayon_core/pipeline/farm/pyblish_functions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/farm/pyblish_functions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index fc9371f719..b9a927f02d 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -679,8 +679,9 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, for aov, files in expected_files[0].items(): collected_files = _collect_expected_files_for_aov(files) - expected_filepath = collected_files[0] \ - if isinstance(collected_files, (list, tuple)) else collected_files + expected_filepath = collected_files + if isinstance(collected_files, (list, tuple)): + expected_filepath = collected_files[0] dynamic_data = { "aov": aov, From 009ab46be04be91c3f94e2e93af5c36476175e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:37:18 +0200 Subject: [PATCH 24/44] Update client/ayon_core/pipeline/farm/pyblish_functions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/farm/pyblish_functions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index b9a927f02d..b61007d0cb 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -619,8 +619,7 @@ def get_product_name_and_group_from_template( # remove 'aov' from data used to format group. See todo comment above # for possible solution. _dynamic_data = deepcopy(dynamic_data) or {} - if _dynamic_data["aov"]: - del _dynamic_data["aov"] + _dynamic_data.pop("aov", None) resulting_group_name = get_product_name( project_name=project_name, task_name=task_entity["name"], From cb4af77a429077bc92ead11bda6b50521e31f2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:37:29 +0200 Subject: [PATCH 25/44] Update client/ayon_core/pipeline/farm/pyblish_functions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/farm/pyblish_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index b61007d0cb..f9ce2d3cf5 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -585,11 +585,11 @@ def _get_legacy_product_name_and_group( def get_product_name_and_group_from_template( - task_entity, project_name, - host_name, + task_entity, product_type, variant, + host_name, dynamic_data=None): """Get product name and group name from template. From e784c0073532414dfb5020af659954ef24c57db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:38:12 +0200 Subject: [PATCH 26/44] Update client/ayon_core/pipeline/farm/pyblish_functions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/farm/pyblish_functions.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index f9ce2d3cf5..e071d46321 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -855,12 +855,11 @@ def _collect_expected_files_for_aov(files): "to render, don't know what to do " "with them.") return rem[0] - else: - # but we really expect only one collection. - # Nothing else make sense. - if len(cols) != 1: - raise ValueError("Only one image sequence type is expected.") # noqa: E501 - return list(cols[0]) + # but we really expect only one collection. + # Nothing else make sense. + if len(cols) != 1: + raise ValueError("Only one image sequence type is expected.") # noqa: E501 + return list(cols[0]) def get_resources(project_name, version_entity, extension=None): From caf674c00827a0338b035a5d9c8b033124a2b25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:38:28 +0200 Subject: [PATCH 27/44] Update client/ayon_core/pipeline/farm/pyblish_functions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/farm/pyblish_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index e071d46321..b218dc78e5 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -725,7 +725,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, project_name=instance.context.data["projectName"], host_name=instance.context.data["hostName"], product_type=skeleton["productType"], - variant=instance.data.get('variant', source_product_name), + variant=instance.data.get("variant", source_product_name), dynamic_data=dynamic_data ) From f752940c1175cfd2bd9665264e218ed0732622c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 19 Aug 2024 16:10:33 +0200 Subject: [PATCH 28/44] :memo: added help for product templates --- .../plugins/publish/help/validate_unique_subsets.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/help/validate_unique_subsets.xml b/client/ayon_core/plugins/publish/help/validate_unique_subsets.xml index e163fc39fe..96b07979b7 100644 --- a/client/ayon_core/plugins/publish/help/validate_unique_subsets.xml +++ b/client/ayon_core/plugins/publish/help/validate_unique_subsets.xml @@ -11,7 +11,11 @@ Multiples instances from your scene are set to publish into the same folder > pr ### How to repair? -Remove the offending instances or rename to have a unique name. +Remove the offending instances or rename to have a unique name. Also, please + check your product name templates to ensure that resolved names are + sufficiently unique. You can find that settings: + + ayon+settings://core/tools/creator/product_name_profiles - \ No newline at end of file + From 5442cacdc346b7ef1c34c5493f116ea82300d531 Mon Sep 17 00:00:00 2001 From: Robin De Lillo Date: Mon, 19 Aug 2024 11:08:44 -0400 Subject: [PATCH 29/44] Update client/ayon_core/lib/path_templates.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/path_templates.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/lib/path_templates.py b/client/ayon_core/lib/path_templates.py index 3e3bdd8f78..33af503dd5 100644 --- a/client/ayon_core/lib/path_templates.py +++ b/client/ayon_core/lib/path_templates.py @@ -475,9 +475,9 @@ def validate_key_is_matched(key): bool: Openings and closing are valid. """ - opening = tuple("({[") - closing = tuple(")}]") - mapping = dict(zip(opening, closing)) + mapping = dict(zip("({[", ")}]")) + opening = set(mapping.keys()) + closing = set(mapping.values()) queue = [] for letter in key: From 9331187000e18c70d4a1d38f159f0ebc4c483f6b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:09:29 +0200 Subject: [PATCH 30/44] ProcessContext also has information about preparation state --- client/ayon_core/addon/base.py | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 383703e2bc..41ba7d4a0e 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -116,6 +116,47 @@ def __init__( if kwargs: unknown_keys = ", ".join([f'"{key}"' for key in kwargs.keys()]) print(f"Unknown keys in ProcessContext: {unknown_keys}") + self._prepared: bool = False + self._exception: Optional[Exception] = None + + def is_prepared(self) -> bool: + """Preparation of process finished. + + Returns: + bool: Preparation is done. + """ + return self._prepared + + def set_prepared(self): + """Mark process as prepared.""" + self._prepared = True + + def preparation_failed(self) -> bool: + """Preparation failed. + + Returns: + bool: Preparation failed. + + """ + return self._exception is not None + + def get_exception(self) -> Optional[Exception]: + """Get exception that occurred during preparation. + + Returns: + Optional[Exception]: Exception that caused preparation fail. + + """ + return self._exception + + def set_exception(self, exception: Exception): + """Set exception that occurred during preparation. + + Args: + exception (Exception): Exception that caused preparation fail. + + """ + self._exception = exception # Inherit from `object` for Python 2 hosts From 501cacc4b8e5e081d7211ddae7a11ad492348e3c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:09:46 +0200 Subject: [PATCH 31/44] change state of context when is done --- client/ayon_core/addon/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index ac5ff25984..5f6922b924 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -134,6 +134,8 @@ def ensure_addons_are_process_context_ready( failed = True break + process_context.set_prepared() + process_context.set_exception(exception) output_str = output.getvalue() # Print stdout/stderr to console as it was redirected print(output_str) From 8e9c28a84a416d51169ccc160d269481b4a026b0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:10:02 +0200 Subject: [PATCH 32/44] 'ensure_addons_are_process_ready' returns ProcessContext --- client/ayon_core/addon/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index 5f6922b924..b301c70564 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -161,7 +161,7 @@ def ensure_addons_are_process_ready( addons_manager: Optional[AddonsManager] = None, exit_on_failure: bool = True, **kwargs, -) -> Optional[Exception]: +) -> ProcessContext: """Ensure all enabled addons are ready to be used in the given context. Call this method only in AYON launcher process and as first thing @@ -181,6 +181,7 @@ def ensure_addons_are_process_ready( """ context: ProcessContext = ProcessContext(**kwargs) - return ensure_addons_are_process_context_ready( + ensure_addons_are_process_context_ready( context, addons_manager, exit_on_failure ) + return context From f3cccccab73bc58e0b8c9daa53ebd9b37b80e74c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:10:16 +0200 Subject: [PATCH 33/44] do not return exception on fail --- client/ayon_core/addon/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index b301c70564..0481ed4cad 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -72,7 +72,7 @@ def ensure_addons_are_process_context_ready( process_context: ProcessContext, addons_manager: Optional[AddonsManager] = None, exit_on_failure: bool = True, -) -> Optional[Exception]: +): """Ensure all enabled addons are ready to be used in the given context. Call this method only in AYON launcher process and as first thing @@ -152,9 +152,8 @@ def ensure_addons_are_process_context_ready( detail = output_str _handle_error(process_context, message, detail) - if not exit_on_failure: - return exception - sys.exit(1) + if exit_on_failure: + sys.exit(1) def ensure_addons_are_process_ready( From 2b46eee1dd2d074e979923ba313086cbbd90a271 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:19:52 +0200 Subject: [PATCH 34/44] added ProcessContext arguments to 'ensure_addons_are_process_ready' --- client/ayon_core/addon/base.py | 3 ++- client/ayon_core/addon/utils.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 41ba7d4a0e..e5b5087423 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -94,7 +94,8 @@ class ProcessContext: project_name (Optional[str]): Project name. Can be filled in case process is triggered for specific project. Some addons can have different behavior based on project. - headless (Optional[bool]): Is process running in headless mode. + headless (Optional[bool]): Is process running in headless mode. Value + is filled with value based on state set in AYON launcher. """ def __init__( diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index 0481ed4cad..ad04586019 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -157,6 +157,10 @@ def ensure_addons_are_process_context_ready( def ensure_addons_are_process_ready( + addon_name: Optional[str] = None, + addon_version: Optional[str] = None, + project_name: Optional[str] = None, + headless: Optional[bool] = None, addons_manager: Optional[AddonsManager] = None, exit_on_failure: bool = True, **kwargs, @@ -168,6 +172,13 @@ def ensure_addons_are_process_ready( should not be created. Args: + addon_name (Optional[str]): Addon name which triggered process. + addon_version (Optional[str]): Addon version which triggered process. + project_name (Optional[str]): Project name. Can be filled in case + process is triggered for specific project. Some addons can have + different behavior based on project. + headless (Optional[bool]): Is process running in headless mode. Value + is filled with value based on state set in AYON launcher. addons_manager (Optional[AddonsManager]): The addons manager to use. If not provided, a new one will be created. exit_on_failure (bool, optional): If True, the process will exit @@ -179,7 +190,13 @@ def ensure_addons_are_process_ready( preparation, if any. """ - context: ProcessContext = ProcessContext(**kwargs) + context: ProcessContext = ProcessContext( + addon_name, + addon_version, + project_name, + headless, + **kwargs + ) ensure_addons_are_process_context_ready( context, addons_manager, exit_on_failure ) From d983879ccd7f4215c63d9baa52b4e0b16607c62e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:39:56 +0200 Subject: [PATCH 35/44] added comment to project name --- client/ayon_core/addon/base.py | 2 +- client/ayon_core/addon/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index e5b5087423..5662375c0a 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -93,7 +93,7 @@ class ProcessContext: addon_version (Optional[str]): Addon version which triggered process. project_name (Optional[str]): Project name. Can be filled in case process is triggered for specific project. Some addons can have - different behavior based on project. + different behavior based on project. Value is NOT autofilled. headless (Optional[bool]): Is process running in headless mode. Value is filled with value based on state set in AYON launcher. diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index ad04586019..43118bff7e 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -176,7 +176,7 @@ def ensure_addons_are_process_ready( addon_version (Optional[str]): Addon version which triggered process. project_name (Optional[str]): Project name. Can be filled in case process is triggered for specific project. Some addons can have - different behavior based on project. + different behavior based on project. Value is NOT autofilled. headless (Optional[bool]): Is process running in headless mode. Value is filled with value based on state set in AYON launcher. addons_manager (Optional[AddonsManager]): The addons From eb4407b9efb55bf76bdb5b8040422f943bc2410f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:57:56 +0200 Subject: [PATCH 36/44] fix returns in docstrings --- client/ayon_core/addon/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index 43118bff7e..f2441e37be 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -87,10 +87,6 @@ def ensure_addons_are_process_context_ready( exit_on_failure (bool, optional): If True, the process will exit if an error occurs. Defaults to True. - Returns: - Optional[Exception]: The exception that occurred during the - preparation, if any. - """ if addons_manager is None: addons_manager = AddonsManager() @@ -186,7 +182,7 @@ def ensure_addons_are_process_ready( kwargs: The keyword arguments to pass to the ProcessContext. Returns: - Optional[Exception]: The exception that occurred during the + ProcessContext: The exception that occurred during the preparation, if any. """ From 92ce331a302c26a6a9a9fd47361269225d07b098 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 18:45:54 +0200 Subject: [PATCH 37/44] added 'is_headless_mode_enabled' function --- client/ayon_core/addon/base.py | 4 ++-- client/ayon_core/lib/__init__.py | 2 ++ client/ayon_core/lib/ayon_info.py | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 5662375c0a..6343166ac8 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -20,6 +20,7 @@ Logger, is_dev_mode_enabled, get_launcher_storage_dir, + is_headless_mode_enabled, ) from ayon_core.settings import get_studio_settings @@ -107,8 +108,7 @@ def __init__( **kwargs, ): if headless is None: - # TODO use lib function to get headless mode - headless = os.getenv("AYON_HEADLESS_MODE") == "1" + headless = is_headless_mode_enabled() self.addon_name: Optional[str] = addon_name self.addon_version: Optional[str] = addon_version self.project_name: Optional[str] = project_name diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index d4b161031e..0074c4d2bd 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -132,6 +132,7 @@ is_in_ayon_launcher_process, is_running_from_build, is_using_ayon_console, + is_headless_mode_enabled, is_staging_enabled, is_dev_mode_enabled, is_in_tests, @@ -245,6 +246,7 @@ "is_in_ayon_launcher_process", "is_running_from_build", "is_using_ayon_console", + "is_headless_mode_enabled", "is_staging_enabled", "is_dev_mode_enabled", "is_in_tests", diff --git a/client/ayon_core/lib/ayon_info.py b/client/ayon_core/lib/ayon_info.py index c4333fab95..7e194a824e 100644 --- a/client/ayon_core/lib/ayon_info.py +++ b/client/ayon_core/lib/ayon_info.py @@ -78,6 +78,10 @@ def is_using_ayon_console(): return "ayon_console" in executable_filename +def is_headless_mode_enabled(): + return os.getenv("AYON_HEADLESS_MODE") == "1" + + def is_staging_enabled(): return os.getenv("AYON_USE_STAGING") == "1" From 3bc993399117f5690d6597a26947603fafa8d206 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 19:10:57 +0200 Subject: [PATCH 38/44] return bool all the time --- client/ayon_core/addon/utils.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index f2441e37be..31cb128e4b 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -72,7 +72,7 @@ def ensure_addons_are_process_context_ready( process_context: ProcessContext, addons_manager: Optional[AddonsManager] = None, exit_on_failure: bool = True, -): +) -> bool: """Ensure all enabled addons are ready to be used in the given context. Call this method only in AYON launcher process and as first thing @@ -87,6 +87,9 @@ def ensure_addons_are_process_context_ready( exit_on_failure (bool, optional): If True, the process will exit if an error occurs. Defaults to True. + Returns: + bool: True if all addons are ready, False otherwise. + """ if addons_manager is None: addons_manager = AddonsManager() @@ -138,7 +141,7 @@ def ensure_addons_are_process_context_ready( if not failed: if not process_context.headless: _start_tray() - return None + return True detail = None if use_detail: @@ -150,6 +153,7 @@ def ensure_addons_are_process_context_ready( _handle_error(process_context, message, detail) if exit_on_failure: sys.exit(1) + return False def ensure_addons_are_process_ready( @@ -160,7 +164,7 @@ def ensure_addons_are_process_ready( addons_manager: Optional[AddonsManager] = None, exit_on_failure: bool = True, **kwargs, -) -> ProcessContext: +) -> bool: """Ensure all enabled addons are ready to be used in the given context. Call this method only in AYON launcher process and as first thing @@ -182,8 +186,7 @@ def ensure_addons_are_process_ready( kwargs: The keyword arguments to pass to the ProcessContext. Returns: - ProcessContext: The exception that occurred during the - preparation, if any. + bool: True if all addons are ready, False otherwise. """ context: ProcessContext = ProcessContext( @@ -193,7 +196,6 @@ def ensure_addons_are_process_ready( headless, **kwargs ) - ensure_addons_are_process_context_ready( + return ensure_addons_are_process_context_ready( context, addons_manager, exit_on_failure ) - return context From 173b2f27272cb408e4ae496eb5fe40bf3eb4c266 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 19:14:22 +0200 Subject: [PATCH 39/44] remove ProcessContext methods --- client/ayon_core/addon/base.py | 41 --------------------------------- client/ayon_core/addon/utils.py | 7 +----- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 6343166ac8..e627fb2b38 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -117,47 +117,6 @@ def __init__( if kwargs: unknown_keys = ", ".join([f'"{key}"' for key in kwargs.keys()]) print(f"Unknown keys in ProcessContext: {unknown_keys}") - self._prepared: bool = False - self._exception: Optional[Exception] = None - - def is_prepared(self) -> bool: - """Preparation of process finished. - - Returns: - bool: Preparation is done. - """ - return self._prepared - - def set_prepared(self): - """Mark process as prepared.""" - self._prepared = True - - def preparation_failed(self) -> bool: - """Preparation failed. - - Returns: - bool: Preparation failed. - - """ - return self._exception is not None - - def get_exception(self) -> Optional[Exception]: - """Get exception that occurred during preparation. - - Returns: - Optional[Exception]: Exception that caused preparation fail. - - """ - return self._exception - - def set_exception(self, exception: Exception): - """Set exception that occurred during preparation. - - Args: - exception (Exception): Exception that caused preparation fail. - - """ - self._exception = exception # Inherit from `object` for Python 2 hosts diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index 31cb128e4b..32b42bf1e2 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -94,7 +94,6 @@ def ensure_addons_are_process_context_ready( if addons_manager is None: addons_manager = AddonsManager() - exception = None message = None failed = False use_detail = False @@ -111,13 +110,11 @@ def ensure_addons_are_process_context_ready( addon.ensure_is_process_ready(process_context) addon_failed = False except ProcessPreparationError as exc: - exception = exc message = str(exc) print(f"Addon preparation failed: '{addon.name}'") print(message) - except BaseException as exc: - exception = exc + except BaseException: use_detail = True message = "An unexpected error occurred." formatted_traceback = "".join(traceback.format_exception( @@ -133,8 +130,6 @@ def ensure_addons_are_process_context_ready( failed = True break - process_context.set_prepared() - process_context.set_exception(exception) output_str = output.getvalue() # Print stdout/stderr to console as it was redirected print(output_str) From 9260a80cb0becbf322a78ad431a7de45b8f29836 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 19:14:53 +0200 Subject: [PATCH 40/44] require addon name and version --- client/ayon_core/addon/base.py | 29 +++++++++++++++++------------ client/ayon_core/addon/utils.py | 8 ++++---- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index e627fb2b38..494c0f3da7 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -81,17 +81,22 @@ class ProcessPreparationError(Exception): class ProcessContext: - """Context of child process. + """Hold context of process that is going to be started. - Notes: - This class is used to pass context to child process. It can be used - to use different behavior of addon based on information in - the context. - The context can be enhanced in future versions. + Right now the context is simple, having information about addon that wants + to trigger preparation and possibly project name for which it should + happen. + + Preparation for process can be required for ayon-core or any other addon. + It can be, change of environment variables, or request login to + a project management. + + At the moment of creation is 'ProcessContext' only data holder, but that + might change in future if there will be need. Args: - addon_name (Optional[str]): Addon name which triggered process. - addon_version (Optional[str]): Addon version which triggered process. + addon_name (str): Addon name which triggered process. + addon_version (str): Addon version which triggered process. project_name (Optional[str]): Project name. Can be filled in case process is triggered for specific project. Some addons can have different behavior based on project. Value is NOT autofilled. @@ -101,16 +106,16 @@ class ProcessContext: """ def __init__( self, - addon_name: Optional[str] = None, - addon_version: Optional[str] = None, + addon_name: str = None, + addon_version: str = None, project_name: Optional[str] = None, headless: Optional[bool] = None, **kwargs, ): if headless is None: headless = is_headless_mode_enabled() - self.addon_name: Optional[str] = addon_name - self.addon_version: Optional[str] = addon_version + self.addon_name: str = addon_name + self.addon_version: str = addon_version self.project_name: Optional[str] = project_name self.headless: bool = headless diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index 32b42bf1e2..1dea4cc4fe 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -152,8 +152,8 @@ def ensure_addons_are_process_context_ready( def ensure_addons_are_process_ready( - addon_name: Optional[str] = None, - addon_version: Optional[str] = None, + addon_name: str, + addon_version: str, project_name: Optional[str] = None, headless: Optional[bool] = None, addons_manager: Optional[AddonsManager] = None, @@ -167,8 +167,8 @@ def ensure_addons_are_process_ready( should not be created. Args: - addon_name (Optional[str]): Addon name which triggered process. - addon_version (Optional[str]): Addon version which triggered process. + addon_name (str): Addon name which triggered process. + addon_version (str): Addon version which triggered process. project_name (Optional[str]): Project name. Can be filled in case process is triggered for specific project. Some addons can have different behavior based on project. Value is NOT autofilled. From e5eefc81fc10db24c232157eb0ab0b08b5ef5017 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 19:15:07 +0200 Subject: [PATCH 41/44] added todo --- client/ayon_core/addon/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index 1dea4cc4fe..8e8c2bd8d7 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -79,6 +79,10 @@ def ensure_addons_are_process_context_ready( to avoid possible clashes with preparation. For example 'QApplication' should not be created. + Todos: + Run all preparations and allow to "ignore" failed preparations. + Right now single addon can block using certain actions. + Args: process_context (ProcessContext): The context in which the addons should be prepared. From e0339aeb104c13b8aada5a728d67104d3041dc3e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 20 Aug 2024 19:25:45 +0200 Subject: [PATCH 42/44] require addons_manager and exit_on_failure as kwargs --- client/ayon_core/addon/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index 8e8c2bd8d7..f983e37d3c 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -160,6 +160,7 @@ def ensure_addons_are_process_ready( addon_version: str, project_name: Optional[str] = None, headless: Optional[bool] = None, + *, addons_manager: Optional[AddonsManager] = None, exit_on_failure: bool = True, **kwargs, From 723198c2a03be93e4408c5c4179bc48b8724ca5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:19:12 +0200 Subject: [PATCH 43/44] use 'title' instead of 'topic' --- server/settings/publish_plugins.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index a5ea7bd762..8ca96432f4 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -562,12 +562,12 @@ class ExtractBurninDef(BaseSettingsModel): _isGroup = True _layout = "expanded" name: str = SettingsField("") - TOP_LEFT: str = SettingsField("", topic="Top Left") - TOP_CENTERED: str = SettingsField("", topic="Top Centered") - TOP_RIGHT: str = SettingsField("", topic="Top Right") - BOTTOM_LEFT: str = SettingsField("", topic="Bottom Left") - BOTTOM_CENTERED: str = SettingsField("", topic="Bottom Centered") - BOTTOM_RIGHT: str = SettingsField("", topic="Bottom Right") + TOP_LEFT: str = SettingsField("", title="Top Left") + TOP_CENTERED: str = SettingsField("", title="Top Centered") + TOP_RIGHT: str = SettingsField("", title="Top Right") + BOTTOM_LEFT: str = SettingsField("", title="Bottom Left") + BOTTOM_CENTERED: str = SettingsField("", title="Bottom Centered") + BOTTOM_RIGHT: str = SettingsField("", title="Bottom Right") filter: ExtractBurninDefFilter = SettingsField( default_factory=ExtractBurninDefFilter, title="Additional filtering" From 725584a8dbb9b29e7dc1f675cf2c174efc268d9d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:29:56 +0200 Subject: [PATCH 44/44] require addon name/version Co-authored-by: Roy Nieterau --- client/ayon_core/addon/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 494c0f3da7..7f0636ccca 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -106,8 +106,8 @@ class ProcessContext: """ def __init__( self, - addon_name: str = None, - addon_version: str = None, + addon_name: str, + addon_version: str, project_name: Optional[str] = None, headless: Optional[bool] = None, **kwargs,