🎨 add support for product base type to basic creator logic

This commit is contained in:
Ondrej Samohel 2025-06-03 17:25:32 +02:00
parent 9e730a6b5b
commit d237e5f54c
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
5 changed files with 119 additions and 56 deletions

View file

@ -32,7 +32,7 @@ from ayon_core.host import IPublishHost, IWorkfileHost
from ayon_core.pipeline import Anatomy
from ayon_core.pipeline.template_data import get_template_data
from ayon_core.pipeline.plugin_discover import DiscoverResult
from ayon_core.pipeline import is_supporting_product_base_type
from ayon_core.pipeline.compatibility import is_supporting_product_base_type
from .exceptions import (
CreatorError,

View file

@ -317,7 +317,6 @@ class BaseCreator(ABC):
identifier = f"{identifier}.{self.product_type}"
return identifier
@property
@abstractmethod
def product_type(self):
@ -562,14 +561,15 @@ class BaseCreator(ABC):
def get_product_name(
self,
project_name,
folder_entity,
task_entity,
variant,
host_name=None,
instance=None,
project_entity=None,
):
project_name: str,
folder_entity: dict[str, Any],
task_entity: dict[str, Any],
variant: str,
host_name: Optional[str] = None,
instance: Optional[CreatedInstance] = None,
project_entity: Optional[dict[str, Any]] = None,
product_base_type: Optional[str] = None,
) -> str:
"""Return product name for passed context.
Method is also called on product name update. In that case origin
@ -586,8 +586,12 @@ class BaseCreator(ABC):
for which is product name updated. Passed only on product name
update.
project_entity (Optional[dict[str, Any]]): Project entity.
product_base_type (Optional[str]): Product base type.
"""
if is_supporting_product_base_type() and (instance and hasattr(instance, "product_base_type")): # noqa: E501
product_base_type = instance.product_base_type
if host_name is None:
host_name = self.create_context.host_name
@ -619,6 +623,7 @@ class BaseCreator(ABC):
dynamic_data=dynamic_data,
project_settings=self.project_settings,
project_entity=project_entity,
product_base_type=product_base_type
)
def get_instance_attr_defs(self):

View file

@ -1,9 +1,16 @@
"""Functions for handling product names."""
from __future__ import annotations
from typing import Any, Optional, Union
from warnings import warn
import ayon_api
from ayon_core.lib import (
StringTemplate,
filter_profiles,
prepare_template_data,
)
from ayon_core.pipeline.compatibility import is_supporting_product_base_type
from ayon_core.settings import get_project_settings
from .constants import DEFAULT_PRODUCT_TEMPLATE
@ -11,14 +18,15 @@ from .exceptions import TaskNotSetError, TemplateFillError
def get_product_name_template(
project_name,
product_type,
task_name,
task_type,
host_name,
default_template=None,
project_settings=None
):
project_name: str,
product_type: str,
task_name: str,
task_type: str,
host_name: str,
default_template: Optional[str] = None,
project_settings: Optional[dict[str, Any]] = None,
product_base_type: Optional[str] = None
) -> str:
"""Get product name template based on passed context.
Args:
@ -28,13 +36,17 @@ def get_product_name_template(
host_name (str): Name of host in which the product name is calculated.
task_name (str): Name of task in which context the product is created.
task_type (str): Type of task in which context the product is created.
default_template (Union[str, None]): Default template which is used if
default_template (Optional, str): Default template which is used if
settings won't find any matching possibility. Constant
'DEFAULT_PRODUCT_TEMPLATE' is used if not defined.
project_settings (Union[Dict[str, Any], None]): Prepared settings for
project. Settings are queried if not passed.
"""
product_base_type (Optional[str]): Base type of product.
Returns:
str: Product name template.
"""
if project_settings is None:
project_settings = get_project_settings(project_name)
tools_settings = project_settings["core"]["tools"]
@ -46,6 +58,15 @@ def get_product_name_template(
"task_types": task_type
}
if is_supporting_product_base_type():
if product_base_type:
filtering_criteria["product_base_types"] = product_base_type
else:
warn(
"Product base type is not provided, please update your"
"creation code to include it. It will be required in "
"the future.", DeprecationWarning, stacklevel=2)
matching_profile = filter_profiles(profiles, filtering_criteria)
template = None
if matching_profile:
@ -70,17 +91,18 @@ def get_product_name_template(
def get_product_name(
project_name,
task_name,
task_type,
host_name,
product_type,
variant,
default_template=None,
dynamic_data=None,
project_settings=None,
product_type_filter=None,
project_entity=None,
project_name: str,
task_name: str,
task_type: str,
host_name: str,
product_type: str,
variant: str,
default_template: Optional[str] = None,
dynamic_data: Optional[dict[str, Any]] = None,
project_settings: Optional[dict[str, Any]] = None,
product_type_filter: Optional[str] = None,
project_entity: Optional[dict[str, Any]] = None,
product_base_type: Optional[str] = None
):
"""Calculate product name based on passed context and AYON settings.
@ -92,14 +114,20 @@ def get_product_name(
That's main reason why so many arguments are required to calculate product
name.
Deprecation:
The `product_base_type` argument is optional now, but it will be
mandatory in future versions. It is recommended to pass it now to
avoid issues in the future. If it is not passed, a warning will be raised
to inform about this change.
Todos:
Find better filtering options to avoid requirement of
argument 'family_filter'.
Args:
project_name (str): Project name.
task_name (Union[str, None]): Task name.
task_type (Union[str, None]): Task type.
task_name (str): Task name.
task_type (str): Task type.
host_name (str): Host name.
product_type (str): Product type.
variant (str): In most of the cases it is user input during creation.
@ -115,6 +143,8 @@ def get_product_name(
not passed.
project_entity (Optional[Dict[str, Any]]): Project entity used when
task short name is required by template.
product_base_type (Optional[str]): Base type of product.
This will be mandatory in future versions.
Returns:
str: Product name.
@ -129,13 +159,14 @@ def get_product_name(
return ""
template = get_product_name_template(
project_name,
product_type_filter or product_type,
task_name,
task_type,
host_name,
project_name=project_name,
product_type=product_type_filter or product_type,
task_name=task_name,
task_type=task_type,
host_name=host_name,
default_template=default_template,
project_settings=project_settings
project_settings=project_settings,
product_base_type=product_base_type,
)
# Simple check of task name existence for template with {task} in
# - missing task should be possible only in Standalone publisher
@ -147,7 +178,7 @@ def get_product_name(
"type": task_type,
}
if "{task}" in template.lower():
task_value = task_name
task_value["name"] = task_name
elif "{task[short]}" in template.lower():
if project_entity is None:
@ -159,14 +190,25 @@ def get_product_name(
task_short = task_types_by_name.get(task_type, {}).get("shortName")
task_value["short"] = task_short
fill_pairs = {
# look what we have to do to make mypy happy. We should stop using
# those undefined dict based types.
product: dict[str, str] = {"type": product_type}
if is_supporting_product_base_type():
if product_base_type:
product["baseType"] = product_base_type
elif "{product[basetype]}" in template.lower():
warn(
"You have Product base type in product name template,"
"but it is not provided by the creator, please update your"
"creation code to include it. It will be required in "
"the future.", DeprecationWarning, stacklevel=2)
fill_pairs: dict[str, Union[str, dict[str, str]]] = {
"variant": variant,
"family": product_type,
"task": task_value,
"product": {
"type": product_type
}
"product": product,
}
if dynamic_data:
# Dynamic data may override default values
for key, value in dynamic_data.items():
@ -178,7 +220,8 @@ def get_product_name(
data=prepare_template_data(fill_pairs)
)
except KeyError as exp:
raise TemplateFillError(
"Value for {} key is missing in template '{}'."
" Available values are {}".format(str(exp), template, fill_pairs)
msg = (
f"Value for {exp} key is missing in template '{template}'."
f" Available values are {fill_pairs}"
)
raise TemplateFillError(msg) from exp

View file

@ -3,7 +3,7 @@ import collections
from uuid import uuid4
import typing
from typing import Optional, Dict, List, Any
import warnings
from warnings import warn
from ayon_core.lib.attribute_definitions import (
AbstractAttrDef,
@ -465,6 +465,10 @@ class CreatedInstance:
data (Dict[str, Any]): Data used for filling product name or override
data from already existing instance.
creator (BaseCreator): Creator responsible for instance.
product_base_type (Optional[str]): Product base type that will be
created. If not provided then product base type is taken from
creator plugin. If creator does not have product base type then
deprecation warning is raised.
"""
# Keys that can't be changed or removed from data after loading using
@ -497,14 +501,18 @@ class CreatedInstance:
transient_data: Optional[Dict[str, Any]] = None,
product_base_type: Optional[str] = None
):
if is_supporting_product_base_type() and product_base_type is None:
warnings.warn(
f"Creator {creator!r} doesn't support "
"product base type. This will be required in future.",
DeprecationWarning,
stacklevel=2
)
"""Initialize CreatedInstance."""
if is_supporting_product_base_type():
if not hasattr(creator, "product_base_type"):
warn(
f"Provided creator {creator!r} doesn't have "
"product base type attribute defined. This will be "
"required in future.",
DeprecationWarning,
stacklevel=2
)
elif not product_base_type:
product_base_type = creator.product_base_type
self._creator = creator
creator_identifier = creator.identifier

View file

@ -34,6 +34,8 @@ from ayon_core.pipeline.create import (
ConvertorsOperationFailed,
ConvertorItem,
)
from ayon_core.pipeline.compatibility import is_supporting_product_base_type
from ayon_core.tools.publisher.abstract import (
AbstractPublisherBackend,
CardMessageTypes,
@ -631,12 +633,17 @@ class CreateModel:
"instance": instance,
"project_entity": project_entity,
}
if is_supporting_product_base_type() and hasattr(creator, "product_base_type"): # noqa: E501
kwargs["product_base_type"] = creator.product_base_type
# Backwards compatibility for 'project_entity' argument
# - 'get_product_name' signature changed 24/07/08
if not is_func_signature_supported(
creator.get_product_name, *args, **kwargs
):
kwargs.pop("project_entity")
kwargs.pop("product_base_type")
return creator.get_product_name(*args, **kwargs)
def create(