From d1bae6d167e086333bd70ad637d52216981c8fac Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:29:48 +0200 Subject: [PATCH 01/10] don't store 'attr_plugins' --- client/ayon_core/pipeline/create/structures.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index 4f7caa6e11..45839ddaf5 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -252,9 +252,6 @@ def __init__(self, parent, origin_data, attr_plugins=None): self.parent = parent self._origin_data = copy.deepcopy(origin_data) - attr_plugins = attr_plugins or [] - self.attr_plugins = attr_plugins - self._data = copy.deepcopy(origin_data) self._plugin_names_order = [] self._missing_plugins = [] @@ -325,10 +322,9 @@ def origin_data(self): def set_publish_plugins(self, attr_plugins): """Set publish plugins attribute definitions.""" - + attr_plugins = attr_plugins or [] self._plugin_names_order = [] self._missing_plugins = [] - self.attr_plugins = attr_plugins or [] origin_data = self._origin_data data = self._data From c337f2cc281c1d91967b9aa14fe1b19d1ea075fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:50:47 +0200 Subject: [PATCH 02/10] added 2 new methods to be able to receive attribute definitions per instance --- .../pipeline/publish/publish_plugins.py | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/publish_plugins.py b/client/ayon_core/pipeline/publish/publish_plugins.py index 6b1984d92b..3593e781b4 100644 --- a/client/ayon_core/pipeline/publish/publish_plugins.py +++ b/client/ayon_core/pipeline/publish/publish_plugins.py @@ -1,6 +1,7 @@ import inspect from abc import ABCMeta import pyblish.api +import pyblish.logic from pyblish.plugin import MetaPlugin, ExplicitMetaPlugin from ayon_core.lib import BoolDef @@ -114,10 +115,53 @@ def get_attribute_defs(cls): """Publish attribute definitions. Attributes available for all families in plugin's `families` attribute. + + Returns: + list[AbstractAttrDef]: Attribute definitions for plugin. + + """ + return [] + + @classmethod + def get_attribute_defs_for_context(cls, create_context): + """Publish attribute definitions for context. + + Attributes available for all families in plugin's `families` attribute. + + Args: + create_context (CreateContext): Create context. + + Returns: + list[AbstractAttrDef]: Attribute definitions for plugin. + + """ + if cls.__instanceEnabled__: + return [] + return cls.get_attribute_defs() + + @classmethod + def get_attribute_defs_for_instance(cls, create_context, instance): + """Publish attribute definitions for an instance. + + Attributes available for all families in plugin's `families` attribute. + + + Args: + create_context (CreateContext): Create context. + instance (CreatedInstance): Instance for which attributes are + collected. + Returns: - list: Attribute definitions for plugin. + list[AbstractAttrDef]: Attribute definitions for plugin. + """ + if not cls.__instanceEnabled__: + return [] + for _ in pyblish.logic.plugins_by_families( + [cls], [instance.product_type] + ): + return cls.get_attribute_defs() return [] @classmethod From 738cc82f2617bd1d336926535d00ac7c0e3ee03e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:48:12 +0200 Subject: [PATCH 03/10] added helper methods to create plugin --- .../pipeline/create/creator_plugins.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 61c10ee736..3b90b11a51 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import copy import collections -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Dict, Any from abc import ABC, abstractmethod @@ -19,11 +19,12 @@ from .product_name import get_product_name from .utils import get_next_versions_for_instances from .legacy_create import LegacyCreator +from .structures import CreatedInstance if TYPE_CHECKING: from ayon_core.lib import AbstractAttrDef # Avoid cyclic imports - from .context import CreateContext, CreatedInstance, UpdateData # noqa: F401 + from .context import CreateContext, UpdateData # noqa: F401 class ProductConvertorPlugin(ABC): @@ -362,6 +363,18 @@ def log(self): self._log = Logger.get_logger(self.__class__.__name__) return self._log + def _create_instance( + self, product_type: str, product_name: str, data: Dict[str, Any] + ) -> CreatedInstance: + instance = CreatedInstance( + product_type, + product_name, + data, + creator=self, + ) + self._add_instance_to_context(instance) + return instance + def _add_instance_to_context(self, instance): """Helper method to add instance to create context. @@ -551,6 +564,16 @@ def get_instance_attr_defs(self): return self.instance_attr_defs + def get_attr_defs_for_instance(self, instance): + """Get attribute definitions for an instance. + + Args: + instance (CreatedInstance): Instance for which to get + attribute definitions. + + """ + return self.get_instance_attr_defs() + @property def collection_shared_data(self): """Access to shared data that can be used during creator's collection. From cb3df926a9f31373c9728135c277e2a59aea5182 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:59:56 +0200 Subject: [PATCH 04/10] attribute definitions are defined per instance --- client/ayon_core/pipeline/create/context.py | 88 +++++++-------- .../ayon_core/pipeline/create/structures.py | 103 +++++++----------- .../pipeline/publish/publish_plugins.py | 24 ++-- 3 files changed, 86 insertions(+), 129 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 3f067427fa..3387a5a5fa 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -6,7 +6,7 @@ import collections import inspect from contextlib import contextmanager -from typing import Optional +from typing import Optional, Dict, Any, Callable import pyblish.logic import pyblish.api @@ -171,7 +171,6 @@ def __init__( self.publish_plugins_mismatch_targets = [] self.publish_plugins = [] self.plugins_with_defs = [] - self._attr_plugins_by_product_type = {} # Helpers for validating context of collected instances # - they can be validation for multiple instances at one time @@ -564,9 +563,6 @@ def _reset_publish_plugins(self, discover_publish_plugins): publish_plugins_discover ) - # Reset publish plugins - self._attr_plugins_by_product_type = {} - discover_result = DiscoverResult(pyblish.api.Plugin) plugins_with_defs = [] plugins_by_targets = [] @@ -694,11 +690,29 @@ def reset_context_data(self): publish_attributes = original_data.get("publish_attributes") or {} - attr_plugins = self._get_publish_plugins_with_attr_for_context() self._publish_attributes = PublishAttributes( - self, publish_attributes, attr_plugins + self, publish_attributes ) + for plugin in self.plugins_with_defs: + if is_func_signature_supported( + plugin.convert_attribute_values, self, None + ): + plugin.convert_attribute_values(self, None) + + elif not plugin.__instanceEnabled__: + output = plugin.convert_attribute_values(publish_attributes) + if output: + publish_attributes.update(output) + + for plugin in self.plugins_with_defs: + attr_defs = plugin.get_attribute_defs_for_context(self) + if not attr_defs: + continue + self._publish_attributes.set_publish_plugin_attr_defs( + plugin.__name__, attr_defs + ) + def context_data_to_store(self): """Data that should be stored by host function. @@ -734,11 +748,25 @@ def creator_adds_instance(self, instance): return self._instances_by_id[instance.id] = instance + + publish_attributes = instance.publish_attributes # Prepare publish plugin attributes and set it on instance - attr_plugins = self._get_publish_plugins_with_attr_for_product_type( - instance.product_type - ) - instance.set_publish_plugins(attr_plugins) + for plugin in self.plugins_with_defs: + if is_func_signature_supported( + plugin.convert_attribute_values, self, instance + ): + plugin.convert_attribute_values(self, instance) + + elif plugin.__instanceEnabled__: + output = plugin.convert_attribute_values(publish_attributes) + if output: + publish_attributes.update(output) + + for plugin in self.plugins_with_defs: + attr_defs = plugin.get_attribute_defs_for_instance(self, instance) + if not attr_defs: + continue + instance.set_publish_plugin_attr_defs(plugin.__name__, attr_defs) # Add instance to be validated inside 'bulk_instances_collection' # context manager if is inside bulk @@ -1309,44 +1337,6 @@ def remove_instances(self, instances): if failed_info: raise CreatorsRemoveFailed(failed_info) - def _get_publish_plugins_with_attr_for_product_type(self, product_type): - """Publish plugin attributes for passed product type. - - Attribute definitions for specific product type are cached. - - Args: - product_type(str): Instance product type for which should be - attribute definitions returned. - """ - - if product_type not in self._attr_plugins_by_product_type: - import pyblish.logic - - filtered_plugins = pyblish.logic.plugins_by_families( - self.plugins_with_defs, [product_type] - ) - plugins = [] - for plugin in filtered_plugins: - if plugin.__instanceEnabled__: - plugins.append(plugin) - self._attr_plugins_by_product_type[product_type] = plugins - - return self._attr_plugins_by_product_type[product_type] - - def _get_publish_plugins_with_attr_for_context(self): - """Publish plugins attributes for Context plugins. - - Returns: - List[pyblish.api.Plugin]: Publish plugins that have attribute - definitions for context. - """ - - plugins = [] - for plugin in self.plugins_with_defs: - if not plugin.__instanceEnabled__: - plugins.append(plugin) - return plugins - @property def collection_shared_data(self): """Access to shared data that can be used during creator's collection. diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index 45839ddaf5..81b0ce7d6d 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -4,6 +4,7 @@ from ayon_core.lib.attribute_definitions import ( UnknownDef, + UIDef, serialize_attr_defs, deserialize_attr_defs, ) @@ -248,15 +249,11 @@ class PublishAttributes: plugins that may have defined attribute definitions. """ - def __init__(self, parent, origin_data, attr_plugins=None): + def __init__(self, parent, origin_data): self.parent = parent self._origin_data = copy.deepcopy(origin_data) self._data = copy.deepcopy(origin_data) - self._plugin_names_order = [] - self._missing_plugins = [] - - self.set_publish_plugins(attr_plugins) def __getitem__(self, key): return self._data[key] @@ -287,10 +284,9 @@ def pop(self, key, default=None): if key not in self._data: return default - if key in self._missing_plugins: - self._missing_plugins.remove(key) - removed_item = self._data.pop(key) - return removed_item.data_to_store() + value = self._data[key] + if not isinstance(value, AttributeValues): + return self._data.pop(key) value_item = self._data[key] # Prepare value to return @@ -299,12 +295,6 @@ def pop(self, key, default=None): value_item.reset_values() return output - def plugin_names_order(self): - """Plugin names order by their 'order' attribute.""" - - for name in self._plugin_names_order: - yield name - def mark_as_stored(self): self._origin_data = copy.deepcopy(self.data_to_store()) @@ -320,40 +310,29 @@ def data_to_store(self): def origin_data(self): return copy.deepcopy(self._origin_data) - def set_publish_plugins(self, attr_plugins): - """Set publish plugins attribute definitions.""" - attr_plugins = attr_plugins or [] - self._plugin_names_order = [] - self._missing_plugins = [] + def set_publish_plugin_attr_defs(self, plugin_name, attr_defs): + """Set attribute definitions for plugin. - origin_data = self._origin_data - data = self._data - self._data = {} - added_keys = set() - for plugin in attr_plugins: - output = plugin.convert_attribute_values(data) - if output is not None: - data = output - attr_defs = plugin.get_attribute_defs() - if not attr_defs: - continue + Args: + plugin_name(str): Name of plugin. + attr_defs(Optional[List[AbstractAttrDef]]): Attribute definitions. - key = plugin.__name__ - added_keys.add(key) - self._plugin_names_order.append(key) + """ + # TODO what if 'attr_defs' is 'None'? + value = self._data.get(plugin_name) + if value is None: + value = {} - value = data.get(key) or {} - orig_value = copy.deepcopy(origin_data.get(key) or {}) - self._data[key] = PublishAttributeValues( - self, attr_defs, value, orig_value - ) + for attr_def in attr_defs: + if isinstance(attr_def, (UIDef, UnknownDef)): + continue + key = attr_def.key + if key in value: + value[key] = attr_def.convert_value(value[key]) - for key, value in data.items(): - if key not in added_keys: - self._missing_plugins.append(key) - self._data[key] = PublishAttributeValues( - self, [], value, value - ) + self._data[plugin_name] = PublishAttributeValues( + self, attr_defs, value, value + ) def serialize_attributes(self): return { @@ -361,14 +340,9 @@ def serialize_attributes(self): plugin_name: attrs_value.get_serialized_attr_defs() for plugin_name, attrs_value in self._data.items() }, - "plugin_names_order": self._plugin_names_order, - "missing_plugins": self._missing_plugins } def deserialize_attributes(self, data): - self._plugin_names_order = data["plugin_names_order"] - self._missing_plugins = data["missing_plugins"] - attr_defs = deserialize_attr_defs(data["attr_defs"]) origin_data = self._origin_data @@ -386,10 +360,7 @@ def deserialize_attributes(self, data): for key, value in data.items(): if key not in added_keys: - self._missing_plugins.append(key) - self._data[key] = PublishAttributeValues( - self, [], value, value - ) + self._data[key] = value class CreatedInstance: @@ -445,7 +416,6 @@ def __init__( creator_identifier = creator.identifier group_label = creator.get_group_label() creator_label = creator.label - creator_attr_defs = creator.get_instance_attr_defs() self._creator_label = creator_label self._group_label = group_label or creator_identifier @@ -505,6 +475,9 @@ def __init__( # {key: value} creator_values = copy.deepcopy(orig_creator_attributes) + if creator is not None: + creator_attr_defs = creator.get_attr_defs_for_instance(self) + self._data["creator_attributes"] = CreatorAttributeValues( self, list(creator_attr_defs), @@ -514,9 +487,8 @@ def __init__( # Stored publish specific attribute values # {: {key: value}} - # - must be set using 'set_publish_plugins' self._data["publish_attributes"] = PublishAttributes( - self, orig_publish_attributes, None + self, orig_publish_attributes ) if data: self._data.update(data) @@ -745,18 +717,17 @@ def from_existing(cls, instance_data, creator): product_type, product_name, instance_data, creator ) - def set_publish_plugins(self, attr_plugins): - """Set publish plugins with attribute definitions. - - This method should be called only from 'CreateContext'. + def set_publish_plugin_attr_defs(self, plugin_name, attr_defs): + """Set attribute definitions for publish plugin. Args: - attr_plugins (List[pyblish.api.Plugin]): Pyblish plugins which - inherit from 'AYONPyblishPluginMixin' and may contain - attribute definitions. - """ + plugin_name(str): Name of publish plugin. + attr_defs(List[AbstractAttrDef]): Attribute definitions. - self.publish_attributes.set_publish_plugins(attr_plugins) + """ + self.publish_attributes.set_publish_plugin_attr_defs( + plugin_name, attr_defs + ) def add_members(self, members): """Currently unused method.""" diff --git a/client/ayon_core/pipeline/publish/publish_plugins.py b/client/ayon_core/pipeline/publish/publish_plugins.py index 3593e781b4..6ea2cb1efa 100644 --- a/client/ayon_core/pipeline/publish/publish_plugins.py +++ b/client/ayon_core/pipeline/publish/publish_plugins.py @@ -165,20 +165,16 @@ def get_attribute_defs_for_instance(cls, create_context, instance): return [] @classmethod - def convert_attribute_values(cls, attribute_values): - if cls.__name__ not in attribute_values: - return attribute_values - - plugin_values = attribute_values[cls.__name__] - - attr_defs = cls.get_attribute_defs() - for attr_def in attr_defs: - key = attr_def.key - if key in plugin_values: - plugin_values[key] = attr_def.convert_value( - plugin_values[key] - ) - return attribute_values + def convert_attribute_values(cls, create_context, instance): + """Convert attribute values for instance. + + Args: + create_context (CreateContext): Create context. + instance (CreatedInstance): Instance for which attributes are + converted. + + """ + return @staticmethod def get_attr_values_from_data_for_plugin(plugin, data): From 35d727747787d6f67bc73fb26bb2563e0fb5c969 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:43:03 +0200 Subject: [PATCH 05/10] removed unused imports --- client/ayon_core/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 3387a5a5fa..81e35a1c11 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -6,7 +6,7 @@ import collections import inspect from contextlib import contextmanager -from typing import Optional, Dict, Any, Callable +from typing import Optional import pyblish.logic import pyblish.api From d970a1cf20d5440301c48203efa9e131e0c39bc9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:25:37 +0200 Subject: [PATCH 06/10] remove line --- client/ayon_core/pipeline/publish/publish_plugins.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/publish_plugins.py b/client/ayon_core/pipeline/publish/publish_plugins.py index 6ea2cb1efa..147b1d3a6d 100644 --- a/client/ayon_core/pipeline/publish/publish_plugins.py +++ b/client/ayon_core/pipeline/publish/publish_plugins.py @@ -145,7 +145,6 @@ def get_attribute_defs_for_instance(cls, create_context, instance): Attributes available for all families in plugin's `families` attribute. - Args: create_context (CreateContext): Create context. instance (CreatedInstance): Instance for which attributes are From f03983dfdac916f1d5b4d1c74a4e63ba7e905e92 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:50:09 +0200 Subject: [PATCH 07/10] use 'get_attr_defs_for_instance' after creation --- client/ayon_core/pipeline/create/structures.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index 81b0ce7d6d..c725a98e51 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -475,12 +475,13 @@ def __init__( # {key: value} creator_values = copy.deepcopy(orig_creator_attributes) - if creator is not None: - creator_attr_defs = creator.get_attr_defs_for_instance(self) - + if creator_attr_defs is None: + _creator_attr_defs = [] + else: + _creator_attr_defs = list(creator_attr_defs) self._data["creator_attributes"] = CreatorAttributeValues( self, - list(creator_attr_defs), + _creator_attr_defs, creator_values, orig_creator_attributes ) @@ -499,6 +500,12 @@ def __init__( self._folder_is_valid = self.has_set_folder self._task_is_valid = self.has_set_task + if creator is not None: + creator_attr_defs = creator.get_attr_defs_for_instance(self) + self.update_create_attr_defs( + creator_attr_defs, creator_values + ) + def __str__(self): return ( " Date: Fri, 6 Sep 2024 18:51:25 +0200 Subject: [PATCH 08/10] implemented 'update_create_attr_defs' --- client/ayon_core/pipeline/create/structures.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index c725a98e51..7f31a72b0c 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -698,6 +698,17 @@ def data_to_store(self): return output + def update_create_attr_defs(self, attr_defs, value=None): + if value is not None: + value = self._data["creator_attributes"] + origin_data = self._data["creator_attributes"].origin_data + self._data["creator_attributes"] = CreatorAttributeValues( + self, + attr_defs, + value, + origin_data + ) + @classmethod def from_existing(cls, instance_data, creator): """Convert instance data from workfile to CreatedInstance. From b3f39ba41c20ebc10c365a389efa578d810188f9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:59:28 +0200 Subject: [PATCH 09/10] data to store does not have to have 'AttributeValues' --- client/ayon_core/pipeline/create/structures.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index 7f31a72b0c..afefb911cd 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -300,10 +300,12 @@ def mark_as_stored(self): def data_to_store(self): """Convert attribute values to "data to store".""" - output = {} for key, attr_value in self._data.items(): - output[key] = attr_value.data_to_store() + if isinstance(attr_value, AttributeValues): + output[key] = attr_value.data_to_store() + else: + output[key] = attr_value return output @property From 688c25315834f8e7b86090fb19f3da3fe5ad25f6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:26:00 +0200 Subject: [PATCH 10/10] modified signature of '_create_instance' --- .../pipeline/create/creator_plugins.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 3b90b11a51..66725e7026 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -364,8 +364,25 @@ def log(self): return self._log def _create_instance( - self, product_type: str, product_name: str, data: Dict[str, Any] + self, + product_name: str, + data: Dict[str, Any], + product_type: Optional[str] = None ) -> CreatedInstance: + """Create instance and add instance to context. + + Args: + product_name (str): Product name. + data (Dict[str, Any]): Instance data. + product_type (Optional[str]): Product type, object attribute + 'product_type' is used if not passed. + + Returns: + CreatedInstance: Created instance. + + """ + if product_type is None: + product_type = self.product_type instance = CreatedInstance( product_type, product_name,