mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge branch 'develop' into enhancement/AY-6586_Thumbnail_presets
This commit is contained in:
commit
a4ae90c16a
9 changed files with 573 additions and 145 deletions
|
|
@ -1,4 +1,5 @@
|
|||
"""Package to handle compatibility checks for pipeline components."""
|
||||
import ayon_api
|
||||
|
||||
|
||||
def is_product_base_type_supported() -> bool:
|
||||
|
|
@ -13,4 +14,7 @@ def is_product_base_type_supported() -> bool:
|
|||
bool: True if product base types are supported, False otherwise.
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
if not hasattr(ayon_api, "is_product_base_type_supported"):
|
||||
return False
|
||||
return ayon_api.is_product_base_type_supported()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from typing import (
|
|||
Any,
|
||||
Callable,
|
||||
)
|
||||
from warnings import warn
|
||||
|
||||
import pyblish.logic
|
||||
import pyblish.api
|
||||
|
|
@ -752,13 +753,13 @@ class CreateContext:
|
|||
manual_creators = {}
|
||||
report = discover_creator_plugins(return_report=True)
|
||||
self.creator_discover_result = report
|
||||
for creator_class in report.plugins:
|
||||
if inspect.isabstract(creator_class):
|
||||
self.log.debug(
|
||||
"Skipping abstract Creator {}".format(str(creator_class))
|
||||
)
|
||||
continue
|
||||
for creator_class in report.abstract_plugins:
|
||||
self.log.debug(
|
||||
"Skipping abstract Creator '%s'",
|
||||
str(creator_class)
|
||||
)
|
||||
|
||||
for creator_class in report.plugins:
|
||||
creator_identifier = creator_class.identifier
|
||||
if creator_identifier in creators:
|
||||
self.log.warning(
|
||||
|
|
@ -772,19 +773,17 @@ class CreateContext:
|
|||
creator_class.host_name
|
||||
and creator_class.host_name != self.host_name
|
||||
):
|
||||
self.log.info((
|
||||
"Creator's host name \"{}\""
|
||||
" is not supported for current host \"{}\""
|
||||
).format(creator_class.host_name, self.host_name))
|
||||
self.log.info(
|
||||
(
|
||||
'Creator\'s host name "{}"'
|
||||
' is not supported for current host "{}"'
|
||||
).format(creator_class.host_name, self.host_name)
|
||||
)
|
||||
continue
|
||||
|
||||
# TODO report initialization error
|
||||
try:
|
||||
creator = creator_class(
|
||||
project_settings,
|
||||
self,
|
||||
self.headless
|
||||
)
|
||||
creator = creator_class(project_settings, self, self.headless)
|
||||
except Exception:
|
||||
self.log.error(
|
||||
f"Failed to initialize plugin: {creator_class}",
|
||||
|
|
@ -792,6 +791,19 @@ class CreateContext:
|
|||
)
|
||||
continue
|
||||
|
||||
if not creator.product_base_type:
|
||||
message = (
|
||||
f"Provided creator {creator!r} doesn't have "
|
||||
"product base type attribute defined. This will be "
|
||||
"required in future."
|
||||
)
|
||||
warn(
|
||||
message,
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
self.log.warning(message)
|
||||
|
||||
if not creator.enabled:
|
||||
disabled_creators[creator_identifier] = creator
|
||||
continue
|
||||
|
|
@ -1289,8 +1301,12 @@ class CreateContext:
|
|||
"folderPath": folder_entity["path"],
|
||||
"task": task_entity["name"] if task_entity else None,
|
||||
"productType": creator.product_type,
|
||||
# Add product base type if supported. Fallback to product type
|
||||
"productBaseType": (
|
||||
creator.product_base_type or creator.product_type),
|
||||
"variant": variant
|
||||
}
|
||||
|
||||
if active is not None:
|
||||
if not isinstance(active, bool):
|
||||
self.log.warning(
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import copy
|
||||
import collections
|
||||
from typing import TYPE_CHECKING, Optional, Dict, Any
|
||||
"""Creator plugins for the create process."""
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||
|
||||
from ayon_core.lib import Logger, get_version_from_path
|
||||
from ayon_core.pipeline.plugin_discover import (
|
||||
deregister_plugin,
|
||||
deregister_plugin_path,
|
||||
discover,
|
||||
register_plugin,
|
||||
register_plugin_path,
|
||||
deregister_plugin,
|
||||
deregister_plugin_path
|
||||
)
|
||||
from ayon_core.pipeline.staging_dir import get_staging_dir_info, StagingDir
|
||||
from ayon_core.pipeline.staging_dir import StagingDir, get_staging_dir_info
|
||||
|
||||
from .constants import DEFAULT_VARIANT_VALUE
|
||||
from .product_name import get_product_name
|
||||
|
|
@ -23,6 +24,7 @@ from .structures import CreatedInstance
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from ayon_core.lib import AbstractAttrDef
|
||||
|
||||
# Avoid cyclic imports
|
||||
from .context import CreateContext, UpdateData # noqa: F401
|
||||
|
||||
|
|
@ -66,7 +68,6 @@ class ProductConvertorPlugin(ABC):
|
|||
Returns:
|
||||
logging.Logger: Logger with name of the plugin.
|
||||
"""
|
||||
|
||||
if self._log is None:
|
||||
self._log = Logger.get_logger(self.__class__.__name__)
|
||||
return self._log
|
||||
|
|
@ -82,9 +83,8 @@ class ProductConvertorPlugin(ABC):
|
|||
|
||||
Returns:
|
||||
str: Converted identifier unique for all converters in host.
|
||||
"""
|
||||
|
||||
pass
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def find_instances(self):
|
||||
|
|
@ -94,14 +94,10 @@ class ProductConvertorPlugin(ABC):
|
|||
convert.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def convert(self):
|
||||
"""Conversion code."""
|
||||
|
||||
pass
|
||||
|
||||
@property
|
||||
def create_context(self):
|
||||
"""Quick access to create context.
|
||||
|
|
@ -109,7 +105,6 @@ class ProductConvertorPlugin(ABC):
|
|||
Returns:
|
||||
CreateContext: Context which initialized the plugin.
|
||||
"""
|
||||
|
||||
return self._create_context
|
||||
|
||||
@property
|
||||
|
|
@ -122,7 +117,6 @@ class ProductConvertorPlugin(ABC):
|
|||
Raises:
|
||||
UnavailableSharedData: When called out of collection phase.
|
||||
"""
|
||||
|
||||
return self._create_context.collection_shared_data
|
||||
|
||||
def add_convertor_item(self, label):
|
||||
|
|
@ -131,12 +125,10 @@ class ProductConvertorPlugin(ABC):
|
|||
Args:
|
||||
label (str): Label of item which will show in UI.
|
||||
"""
|
||||
|
||||
self._create_context.add_convertor_item(self.identifier, label)
|
||||
|
||||
def remove_convertor_item(self):
|
||||
"""Remove legacy item from create context when conversion finished."""
|
||||
|
||||
self._create_context.remove_convertor_item(self.identifier)
|
||||
|
||||
|
||||
|
|
@ -155,7 +147,6 @@ class BaseCreator(ABC):
|
|||
create_context (CreateContext): Context which initialized creator.
|
||||
headless (bool): Running in headless mode.
|
||||
"""
|
||||
|
||||
# Label shown in UI
|
||||
label = None
|
||||
group_label = None
|
||||
|
|
@ -219,7 +210,6 @@ class BaseCreator(ABC):
|
|||
Returns:
|
||||
Optional[dict[str, Any]]: Settings values or None.
|
||||
"""
|
||||
|
||||
settings = project_settings.get(category_name)
|
||||
if not settings:
|
||||
return None
|
||||
|
|
@ -265,7 +255,6 @@ class BaseCreator(ABC):
|
|||
Args:
|
||||
project_settings (dict[str, Any]): Project settings.
|
||||
"""
|
||||
|
||||
settings_category = self.settings_category
|
||||
if not settings_category:
|
||||
return
|
||||
|
|
@ -277,18 +266,17 @@ class BaseCreator(ABC):
|
|||
project_settings, settings_category, settings_name
|
||||
)
|
||||
if settings is None:
|
||||
self.log.debug("No settings found for {}".format(cls_name))
|
||||
self.log.debug(f"No settings found for {cls_name}")
|
||||
return
|
||||
|
||||
for key, value in settings.items():
|
||||
# Log out attributes that are not defined on plugin object
|
||||
# - those may be potential dangerous typos in settings
|
||||
if not hasattr(self, key):
|
||||
self.log.debug((
|
||||
"Applying settings to unknown attribute '{}' on '{}'."
|
||||
).format(
|
||||
self.log.debug(
|
||||
"Applying settings to unknown attribute '%s' on '%s'.",
|
||||
key, cls_name
|
||||
))
|
||||
)
|
||||
setattr(self, key, value)
|
||||
|
||||
def register_callbacks(self):
|
||||
|
|
@ -297,23 +285,39 @@ class BaseCreator(ABC):
|
|||
Default implementation does nothing. It can be overridden to register
|
||||
callbacks for creator.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
"""Identifier of creator (must be unique).
|
||||
|
||||
Default implementation returns plugin's product type.
|
||||
"""
|
||||
Default implementation returns plugin's product base type,
|
||||
or falls back to product type if product base type is not set.
|
||||
|
||||
return self.product_type
|
||||
"""
|
||||
identifier = self.product_base_type
|
||||
if not identifier:
|
||||
identifier = self.product_type
|
||||
return identifier
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def product_type(self):
|
||||
"""Family that plugin represents."""
|
||||
|
||||
pass
|
||||
@property
|
||||
def product_base_type(self) -> Optional[str]:
|
||||
"""Base product type that plugin represents.
|
||||
|
||||
Todo (antirotor): This should be required in future - it
|
||||
should be made abstract then.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Base product type that plugin represents.
|
||||
If not set, it is assumed that the creator plugin is obsolete
|
||||
and does not support product base type.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
|
|
@ -322,7 +326,6 @@ class BaseCreator(ABC):
|
|||
Returns:
|
||||
str: Name of a project.
|
||||
"""
|
||||
|
||||
return self.create_context.project_name
|
||||
|
||||
@property
|
||||
|
|
@ -332,7 +335,6 @@ class BaseCreator(ABC):
|
|||
Returns:
|
||||
Anatomy: Project anatomy object.
|
||||
"""
|
||||
|
||||
return self.create_context.project_anatomy
|
||||
|
||||
@property
|
||||
|
|
@ -344,13 +346,14 @@ class BaseCreator(ABC):
|
|||
|
||||
Default implementation use attributes in this order:
|
||||
- 'group_label' -> 'label' -> 'identifier'
|
||||
Keep in mind that 'identifier' use 'product_type' by default.
|
||||
|
||||
Keep in mind that 'identifier' uses 'product_base_type' by default.
|
||||
|
||||
Returns:
|
||||
str: Group label that can be used for grouping of instances in UI.
|
||||
Group label can be overridden by instance itself.
|
||||
"""
|
||||
Group label can be overridden by the instance itself.
|
||||
|
||||
"""
|
||||
if self._cached_group_label is None:
|
||||
label = self.identifier
|
||||
if self.group_label:
|
||||
|
|
@ -367,7 +370,6 @@ class BaseCreator(ABC):
|
|||
Returns:
|
||||
logging.Logger: Logger with name of the plugin.
|
||||
"""
|
||||
|
||||
if self._log is None:
|
||||
self._log = Logger.get_logger(self.__class__.__name__)
|
||||
return self._log
|
||||
|
|
@ -376,7 +378,8 @@ class BaseCreator(ABC):
|
|||
self,
|
||||
product_name: str,
|
||||
data: Dict[str, Any],
|
||||
product_type: Optional[str] = None
|
||||
product_type: Optional[str] = None,
|
||||
product_base_type: Optional[str] = None
|
||||
) -> CreatedInstance:
|
||||
"""Create instance and add instance to context.
|
||||
|
||||
|
|
@ -385,6 +388,8 @@ class BaseCreator(ABC):
|
|||
data (Dict[str, Any]): Instance data.
|
||||
product_type (Optional[str]): Product type, object attribute
|
||||
'product_type' is used if not passed.
|
||||
product_base_type (Optional[str]): Product base type, object
|
||||
attribute 'product_base_type' is used if not passed.
|
||||
|
||||
Returns:
|
||||
CreatedInstance: Created instance.
|
||||
|
|
@ -392,11 +397,16 @@ class BaseCreator(ABC):
|
|||
"""
|
||||
if product_type is None:
|
||||
product_type = self.product_type
|
||||
|
||||
if not product_base_type and not self.product_base_type:
|
||||
product_base_type = product_type
|
||||
|
||||
instance = CreatedInstance(
|
||||
product_type,
|
||||
product_name,
|
||||
data,
|
||||
product_type=product_type,
|
||||
product_name=product_name,
|
||||
data=data,
|
||||
creator=self,
|
||||
product_base_type=product_base_type,
|
||||
)
|
||||
self._add_instance_to_context(instance)
|
||||
return instance
|
||||
|
|
@ -412,7 +422,6 @@ class BaseCreator(ABC):
|
|||
Args:
|
||||
instance (CreatedInstance): New created instance.
|
||||
"""
|
||||
|
||||
self.create_context.creator_adds_instance(instance)
|
||||
|
||||
def _remove_instance_from_context(self, instance):
|
||||
|
|
@ -425,7 +434,6 @@ class BaseCreator(ABC):
|
|||
Args:
|
||||
instance (CreatedInstance): Instance which should be removed.
|
||||
"""
|
||||
|
||||
self.create_context.creator_removed_instance(instance)
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -437,8 +445,6 @@ class BaseCreator(ABC):
|
|||
implementation
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def collect_instances(self):
|
||||
"""Collect existing instances related to this creator plugin.
|
||||
|
|
@ -464,8 +470,6 @@ class BaseCreator(ABC):
|
|||
```
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_instances(self, update_list):
|
||||
"""Store changes of existing instances so they can be recollected.
|
||||
|
|
@ -475,8 +479,6 @@ class BaseCreator(ABC):
|
|||
contain changed instance and it's changes.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def remove_instances(self, instances):
|
||||
"""Method called on instance removal.
|
||||
|
|
@ -489,14 +491,11 @@ class BaseCreator(ABC):
|
|||
removed.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def get_icon(self):
|
||||
"""Icon of creator (product type).
|
||||
|
||||
Can return path to image file or awesome icon name.
|
||||
"""
|
||||
|
||||
return self.icon
|
||||
|
||||
def get_dynamic_data(
|
||||
|
|
@ -512,19 +511,18 @@ class BaseCreator(ABC):
|
|||
|
||||
These may be dynamically created based on current context of workfile.
|
||||
"""
|
||||
|
||||
return {}
|
||||
|
||||
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: Optional[dict[str, Any]],
|
||||
variant: str,
|
||||
host_name: Optional[str] = None,
|
||||
instance: Optional[CreatedInstance] = None,
|
||||
project_entity: Optional[dict[str, Any]] = None,
|
||||
) -> str:
|
||||
"""Return product name for passed context.
|
||||
|
||||
Method is also called on product name update. In that case origin
|
||||
|
|
@ -574,6 +572,8 @@ class BaseCreator(ABC):
|
|||
dynamic_data=dynamic_data,
|
||||
project_settings=self.project_settings,
|
||||
project_entity=project_entity,
|
||||
# until we make product_base_type mandatory
|
||||
product_base_type=self.product_base_type
|
||||
)
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
|
|
@ -583,15 +583,15 @@ class BaseCreator(ABC):
|
|||
and values are stored to metadata for future usage and for publishing
|
||||
purposes.
|
||||
|
||||
NOTE:
|
||||
Convert method should be implemented which should care about updating
|
||||
keys/values when plugin attributes change.
|
||||
Note:
|
||||
Convert method should be implemented which should care about
|
||||
updating keys/values when plugin attributes change.
|
||||
|
||||
Returns:
|
||||
list[AbstractAttrDef]: Attribute definitions that can be tweaked
|
||||
for created instance.
|
||||
"""
|
||||
|
||||
"""
|
||||
return self.instance_attr_defs
|
||||
|
||||
def get_attr_defs_for_instance(self, instance):
|
||||
|
|
@ -614,12 +614,10 @@ class BaseCreator(ABC):
|
|||
Raises:
|
||||
UnavailableSharedData: When called out of collection phase.
|
||||
"""
|
||||
|
||||
return self.create_context.collection_shared_data
|
||||
|
||||
def set_instance_thumbnail_path(self, instance_id, thumbnail_path=None):
|
||||
"""Set path to thumbnail for instance."""
|
||||
|
||||
self.create_context.thumbnail_paths_by_instance_id[instance_id] = (
|
||||
thumbnail_path
|
||||
)
|
||||
|
|
@ -640,7 +638,6 @@ class BaseCreator(ABC):
|
|||
Returns:
|
||||
dict[str, int]: Next versions by instance id.
|
||||
"""
|
||||
|
||||
return get_next_versions_for_instances(
|
||||
self.create_context.project_name, instances
|
||||
)
|
||||
|
|
@ -707,7 +704,6 @@ class Creator(BaseCreator):
|
|||
int: Order in which is creator shown (less == earlier). By default
|
||||
is using Creator's 'order' or processing.
|
||||
"""
|
||||
|
||||
return self.order
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -722,11 +718,9 @@ class Creator(BaseCreator):
|
|||
pre_create_data(dict): Data based on pre creation attributes.
|
||||
Those may affect how creator works.
|
||||
"""
|
||||
|
||||
# instance = CreatedInstance(
|
||||
# self.product_type, product_name, instance_data
|
||||
# )
|
||||
pass
|
||||
|
||||
def get_description(self):
|
||||
"""Short description of product type and plugin.
|
||||
|
|
@ -734,7 +728,6 @@ class Creator(BaseCreator):
|
|||
Returns:
|
||||
str: Short description of product type.
|
||||
"""
|
||||
|
||||
return self.description
|
||||
|
||||
def get_detail_description(self):
|
||||
|
|
@ -745,7 +738,6 @@ class Creator(BaseCreator):
|
|||
Returns:
|
||||
str: Detailed description of product type for artist.
|
||||
"""
|
||||
|
||||
return self.detailed_description
|
||||
|
||||
def get_default_variants(self):
|
||||
|
|
@ -759,7 +751,6 @@ class Creator(BaseCreator):
|
|||
Returns:
|
||||
list[str]: Whisper variants for user input.
|
||||
"""
|
||||
|
||||
return copy.deepcopy(self.default_variants)
|
||||
|
||||
def get_default_variant(self, only_explicit=False):
|
||||
|
|
@ -779,7 +770,6 @@ class Creator(BaseCreator):
|
|||
Returns:
|
||||
str: Variant value.
|
||||
"""
|
||||
|
||||
if only_explicit or self._default_variant:
|
||||
return self._default_variant
|
||||
|
||||
|
|
@ -800,7 +790,6 @@ class Creator(BaseCreator):
|
|||
Returns:
|
||||
str: Variant value.
|
||||
"""
|
||||
|
||||
return self.get_default_variant()
|
||||
|
||||
def _set_default_variant_wrap(self, variant):
|
||||
|
|
@ -812,7 +801,6 @@ class Creator(BaseCreator):
|
|||
Args:
|
||||
variant (str): New default variant value.
|
||||
"""
|
||||
|
||||
self._default_variant = variant
|
||||
|
||||
default_variant = property(
|
||||
|
|
@ -962,7 +950,6 @@ class AutoCreator(BaseCreator):
|
|||
|
||||
def remove_instances(self, instances):
|
||||
"""Skip removal."""
|
||||
pass
|
||||
|
||||
|
||||
def discover_creator_plugins(*args, **kwargs):
|
||||
|
|
@ -1020,7 +1007,6 @@ def cache_and_get_instances(creator, shared_key, list_instances_func):
|
|||
dict[str, dict[str, Any]]: Cached instances by creator identifier from
|
||||
result of passed function.
|
||||
"""
|
||||
|
||||
if shared_key not in creator.collection_shared_data:
|
||||
value = collections.defaultdict(list)
|
||||
for instance in list_instances_func():
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
"""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,
|
||||
|
|
@ -11,14 +17,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: Optional[str],
|
||||
task_type: Optional[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:
|
||||
|
|
@ -26,15 +33,21 @@ def get_product_name_template(
|
|||
product_type (str): Product type for which the product name is
|
||||
calculated.
|
||||
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
|
||||
task_name (Optional[str]): Name of task in which context the
|
||||
product is created.
|
||||
task_type (Optional[str]): Type of task in which context the
|
||||
product is created.
|
||||
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 (Optional[dict[str, Any]]): 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"]
|
||||
|
|
@ -43,9 +56,9 @@ def get_product_name_template(
|
|||
"product_types": product_type,
|
||||
"host_names": host_name,
|
||||
"task_names": task_name,
|
||||
"task_types": task_type
|
||||
"task_types": task_type,
|
||||
"product_base_types": product_base_type,
|
||||
}
|
||||
|
||||
matching_profile = filter_profiles(profiles, filtering_criteria)
|
||||
template = None
|
||||
if matching_profile:
|
||||
|
|
@ -70,17 +83,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: Optional[str],
|
||||
task_type: Optional[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 +106,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 (Optional[str]): Task name.
|
||||
task_type (Optional[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 +135,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 +151,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
|
||||
|
|
@ -159,14 +182,25 @@ def get_product_name(
|
|||
task_short = task_types_by_name.get(task_type, {}).get("shortName")
|
||||
task_value["short"] = task_short
|
||||
|
||||
fill_pairs = {
|
||||
if not product_base_type and "{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
|
||||
"type": product_type,
|
||||
"basetype": product_base_type or product_type,
|
||||
}
|
||||
}
|
||||
|
||||
if dynamic_data:
|
||||
# Dynamic data may override default values
|
||||
for key, value in dynamic_data.items():
|
||||
|
|
@ -178,7 +212,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
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ from ayon_core.lib.attribute_definitions import (
|
|||
serialize_attr_defs,
|
||||
deserialize_attr_defs,
|
||||
)
|
||||
|
||||
|
||||
from ayon_core.pipeline import (
|
||||
AYON_INSTANCE_ID,
|
||||
AVALON_INSTANCE_ID,
|
||||
|
|
@ -480,6 +482,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
|
||||
|
|
@ -490,6 +496,7 @@ class CreatedInstance:
|
|||
"id",
|
||||
"instance_id",
|
||||
"productType",
|
||||
"productBaseType",
|
||||
"creator_identifier",
|
||||
"creator_attributes",
|
||||
"publish_attributes"
|
||||
|
|
@ -509,7 +516,13 @@ class CreatedInstance:
|
|||
data: Dict[str, Any],
|
||||
creator: "BaseCreator",
|
||||
transient_data: Optional[Dict[str, Any]] = None,
|
||||
product_base_type: Optional[str] = None
|
||||
):
|
||||
"""Initialize CreatedInstance."""
|
||||
# fallback to product type for backward compatibility
|
||||
if not product_base_type:
|
||||
product_base_type = creator.product_base_type or product_type
|
||||
|
||||
self._creator = creator
|
||||
creator_identifier = creator.identifier
|
||||
group_label = creator.get_group_label()
|
||||
|
|
@ -562,6 +575,9 @@ class CreatedInstance:
|
|||
self._data["id"] = item_id
|
||||
self._data["productType"] = product_type
|
||||
self._data["productName"] = product_name
|
||||
|
||||
self._data["productBaseType"] = product_base_type
|
||||
|
||||
self._data["active"] = data.get("active", True)
|
||||
self._data["creator_identifier"] = creator_identifier
|
||||
|
||||
|
|
|
|||
|
|
@ -122,7 +122,8 @@ def get_publish_template_name(
|
|||
task_type,
|
||||
project_settings=None,
|
||||
hero=False,
|
||||
logger=None
|
||||
product_base_type: Optional[str] = None,
|
||||
logger=None,
|
||||
):
|
||||
"""Get template name which should be used for passed context.
|
||||
|
||||
|
|
@ -140,17 +141,29 @@ def get_publish_template_name(
|
|||
task_type (str): Task type on which is instance working.
|
||||
project_settings (Dict[str, Any]): Prepared project settings.
|
||||
hero (bool): Template is for hero version publishing.
|
||||
product_base_type (Optional[str]): Product type for which should
|
||||
be found template.
|
||||
logger (logging.Logger): Custom logger used for 'filter_profiles'
|
||||
function.
|
||||
|
||||
Returns:
|
||||
str: Template name which should be used for integration.
|
||||
"""
|
||||
if not product_base_type:
|
||||
msg = (
|
||||
"Argument 'product_base_type' is not provided to"
|
||||
" 'get_publish_template_name' function. This argument"
|
||||
" will be required in future versions."
|
||||
)
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
if logger:
|
||||
logger.warning(msg)
|
||||
|
||||
template = None
|
||||
filter_criteria = {
|
||||
"hosts": host_name,
|
||||
"product_types": product_type,
|
||||
"product_base_types": product_base_type,
|
||||
"task_names": task_name,
|
||||
"task_types": task_type,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from ayon_core.pipeline.publish import (
|
|||
KnownPublishError,
|
||||
get_publish_template_name,
|
||||
)
|
||||
from ayon_core.pipeline import is_product_base_type_supported
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -367,6 +368,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
folder_entity = instance.data["folderEntity"]
|
||||
product_name = instance.data["productName"]
|
||||
product_type = instance.data["productType"]
|
||||
product_base_type = instance.data.get("productBaseType")
|
||||
|
||||
self.log.debug("Product: {}".format(product_name))
|
||||
|
||||
# Get existing product if it exists
|
||||
|
|
@ -394,14 +397,33 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
product_id = None
|
||||
if existing_product_entity:
|
||||
product_id = existing_product_entity["id"]
|
||||
product_entity = new_product_entity(
|
||||
product_name,
|
||||
product_type,
|
||||
folder_entity["id"],
|
||||
data=data,
|
||||
attribs=attributes,
|
||||
entity_id=product_id
|
||||
)
|
||||
|
||||
new_product_entity_kwargs = {
|
||||
"name": product_name,
|
||||
"product_type": product_type,
|
||||
"folder_id": folder_entity["id"],
|
||||
"data": data,
|
||||
"attribs": attributes,
|
||||
"entity_id": product_id,
|
||||
"product_base_type": product_base_type,
|
||||
}
|
||||
|
||||
if not is_product_base_type_supported():
|
||||
new_product_entity_kwargs.pop("product_base_type")
|
||||
if (
|
||||
product_base_type is not None
|
||||
and product_base_type != product_type):
|
||||
self.log.warning((
|
||||
"Product base type %s is not supported by the server, "
|
||||
"but it's defined - and it differs from product type %s. "
|
||||
"Using product base type as product type."
|
||||
), product_base_type, product_type)
|
||||
|
||||
new_product_entity_kwargs["product_type"] = (
|
||||
product_base_type
|
||||
)
|
||||
|
||||
product_entity = new_product_entity(**new_product_entity_kwargs)
|
||||
|
||||
if existing_product_entity is None:
|
||||
# Create a new product
|
||||
|
|
@ -927,6 +949,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
host_name = context.data["hostName"]
|
||||
anatomy_data = instance.data["anatomyData"]
|
||||
product_type = instance.data["productType"]
|
||||
product_base_type = instance.data.get("productBaseType")
|
||||
task_info = anatomy_data.get("task") or {}
|
||||
|
||||
return get_publish_template_name(
|
||||
|
|
@ -936,7 +959,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
task_name=task_info.get("name"),
|
||||
task_type=task_info.get("type"),
|
||||
project_settings=context.data["project_settings"],
|
||||
logger=self.log
|
||||
logger=self.log,
|
||||
product_base_type=product_base_type
|
||||
)
|
||||
|
||||
def get_rootless_path(self, anatomy, path):
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ from ayon_core.pipeline.create import (
|
|||
ConvertorsOperationFailed,
|
||||
ConvertorItem,
|
||||
)
|
||||
|
||||
from ayon_core.tools.publisher.abstract import (
|
||||
AbstractPublisherBackend,
|
||||
CardMessageTypes,
|
||||
|
|
|
|||
333
tests/client/ayon_core/pipeline/create/test_product_name.py
Normal file
333
tests/client/ayon_core/pipeline/create/test_product_name.py
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
"""Tests for product_name helpers."""
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
from ayon_core.pipeline.create.product_name import (
|
||||
get_product_name_template,
|
||||
get_product_name,
|
||||
)
|
||||
from ayon_core.pipeline.create.constants import DEFAULT_PRODUCT_TEMPLATE
|
||||
from ayon_core.pipeline.create.exceptions import (
|
||||
TaskNotSetError,
|
||||
TemplateFillError,
|
||||
)
|
||||
|
||||
|
||||
class TestGetProductNameTemplate:
|
||||
@patch("ayon_core.pipeline.create.product_name.get_project_settings")
|
||||
@patch("ayon_core.pipeline.create.product_name.filter_profiles")
|
||||
def test_matching_profile_with_replacements(
|
||||
self,
|
||||
mock_filter_profiles,
|
||||
mock_get_settings,
|
||||
):
|
||||
"""Matching profile applies legacy replacement tokens."""
|
||||
mock_get_settings.return_value = {
|
||||
"core": {"tools": {"creator": {"product_name_profiles": []}}}
|
||||
}
|
||||
# The function should replace {task}/{family}/{asset} variants
|
||||
mock_filter_profiles.return_value = {
|
||||
"template": ("{task}-{Task}-{TASK}-{family}-{Family}"
|
||||
"-{FAMILY}-{asset}-{Asset}-{ASSET}")
|
||||
}
|
||||
|
||||
result = get_product_name_template(
|
||||
project_name="proj",
|
||||
product_type="model",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
)
|
||||
assert result == (
|
||||
"{task[name]}-{Task[name]}-{TASK[NAME]}-"
|
||||
"{product[type]}-{Product[type]}-{PRODUCT[TYPE]}-"
|
||||
"{folder[name]}-{Folder[name]}-{FOLDER[NAME]}"
|
||||
)
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_project_settings")
|
||||
@patch("ayon_core.pipeline.create.product_name.filter_profiles")
|
||||
def test_no_matching_profile_uses_default(
|
||||
self,
|
||||
mock_filter_profiles,
|
||||
mock_get_settings,
|
||||
):
|
||||
mock_get_settings.return_value = {
|
||||
"core": {"tools": {"creator": {"product_name_profiles": []}}}
|
||||
}
|
||||
mock_filter_profiles.return_value = None
|
||||
|
||||
assert (
|
||||
get_product_name_template(
|
||||
project_name="proj",
|
||||
product_type="model",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
)
|
||||
== DEFAULT_PRODUCT_TEMPLATE
|
||||
)
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_project_settings")
|
||||
@patch("ayon_core.pipeline.create.product_name.filter_profiles")
|
||||
def test_custom_default_template_used(
|
||||
self,
|
||||
mock_filter_profiles,
|
||||
mock_get_settings,
|
||||
):
|
||||
mock_get_settings.return_value = {
|
||||
"core": {"tools": {"creator": {"product_name_profiles": []}}}
|
||||
}
|
||||
mock_filter_profiles.return_value = None
|
||||
|
||||
custom_default = "{variant}_{family}"
|
||||
assert (
|
||||
get_product_name_template(
|
||||
project_name="proj",
|
||||
product_type="model",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
default_template=custom_default,
|
||||
)
|
||||
== custom_default
|
||||
)
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_project_settings")
|
||||
@patch("ayon_core.pipeline.create.product_name.filter_profiles")
|
||||
def test_product_base_type_added_to_filtering_when_provided(
|
||||
self,
|
||||
mock_filter_profiles,
|
||||
mock_get_settings,
|
||||
):
|
||||
mock_get_settings.return_value = {
|
||||
"core": {"tools": {"creator": {"product_name_profiles": []}}}
|
||||
}
|
||||
mock_filter_profiles.return_value = None
|
||||
|
||||
get_product_name_template(
|
||||
project_name="proj",
|
||||
product_type="model",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_base_type="asset",
|
||||
)
|
||||
args, kwargs = mock_filter_profiles.call_args
|
||||
# args[1] is filtering_criteria
|
||||
assert args[1]["product_base_types"] == "asset"
|
||||
|
||||
|
||||
class TestGetProductName:
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
@patch("ayon_core.pipeline.create.product_name."
|
||||
"StringTemplate.format_strict_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.prepare_template_data")
|
||||
def test_empty_product_type_returns_empty(
|
||||
self, mock_prepare, mock_format, mock_get_tmpl
|
||||
):
|
||||
assert (
|
||||
get_product_name(
|
||||
project_name="proj",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="",
|
||||
variant="Main",
|
||||
)
|
||||
== ""
|
||||
)
|
||||
mock_get_tmpl.assert_not_called()
|
||||
mock_format.assert_not_called()
|
||||
mock_prepare.assert_not_called()
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
@patch("ayon_core.pipeline.create.product_name."
|
||||
"StringTemplate.format_strict_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.prepare_template_data")
|
||||
def test_happy_path(
|
||||
self, mock_prepare, mock_format, mock_get_tmpl
|
||||
):
|
||||
mock_get_tmpl.return_value = "{task[name]}_{product[type]}_{variant}"
|
||||
mock_prepare.return_value = {
|
||||
"task": {"name": "modeling"},
|
||||
"product": {"type": "model"},
|
||||
"variant": "Main",
|
||||
"family": "model",
|
||||
}
|
||||
mock_format.return_value = "modeling_model_Main"
|
||||
|
||||
result = get_product_name(
|
||||
project_name="proj",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="model",
|
||||
variant="Main",
|
||||
)
|
||||
assert result == "modeling_model_Main"
|
||||
mock_get_tmpl.assert_called_once()
|
||||
mock_prepare.assert_called_once()
|
||||
mock_format.assert_called_once()
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
@patch("ayon_core.pipeline.create.product_name."
|
||||
"StringTemplate.format_strict_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.prepare_template_data")
|
||||
def test_product_name_with_base_type(
|
||||
self, mock_prepare, mock_format, mock_get_tmpl
|
||||
):
|
||||
mock_get_tmpl.return_value = (
|
||||
"{task[name]}_{product[basetype]}_{variant}"
|
||||
)
|
||||
mock_prepare.return_value = {
|
||||
"task": {"name": "modeling"},
|
||||
"product": {"type": "model"},
|
||||
"variant": "Main",
|
||||
"family": "model",
|
||||
}
|
||||
mock_format.return_value = "modeling_modelBase_Main"
|
||||
|
||||
result = get_product_name(
|
||||
project_name="proj",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="model",
|
||||
product_base_type="modelBase",
|
||||
variant="Main",
|
||||
)
|
||||
assert result == "modeling_modelBase_Main"
|
||||
mock_get_tmpl.assert_called_once()
|
||||
mock_prepare.assert_called_once()
|
||||
mock_format.assert_called_once()
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
def test_task_required_but_missing_raises(self, mock_get_tmpl):
|
||||
mock_get_tmpl.return_value = "{task[name]}_{variant}"
|
||||
with pytest.raises(TaskNotSetError):
|
||||
get_product_name(
|
||||
project_name="proj",
|
||||
task_name="",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="model",
|
||||
variant="Main",
|
||||
)
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.ayon_api.get_project")
|
||||
@patch("ayon_core.pipeline.create.product_name.StringTemplate."
|
||||
"format_strict_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.prepare_template_data")
|
||||
def test_task_short_name_is_used(
|
||||
self, mock_prepare, mock_format, mock_get_project, mock_get_tmpl
|
||||
):
|
||||
mock_get_tmpl.return_value = "{task[short]}_{variant}"
|
||||
mock_get_project.return_value = {
|
||||
"taskTypes": [{"name": "Modeling", "shortName": "mdl"}]
|
||||
}
|
||||
mock_prepare.return_value = {
|
||||
"task": {
|
||||
"short": "mdl"
|
||||
},
|
||||
"variant": "Main"
|
||||
}
|
||||
mock_format.return_value = "mdl_Main"
|
||||
|
||||
result = get_product_name(
|
||||
project_name="proj",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="model",
|
||||
variant="Main",
|
||||
)
|
||||
assert result == "mdl_Main"
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.StringTemplate."
|
||||
"format_strict_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.prepare_template_data")
|
||||
def test_template_fill_error_translated(
|
||||
self, mock_prepare, mock_format, mock_get_tmpl
|
||||
):
|
||||
mock_get_tmpl.return_value = "{missing_key}_{variant}"
|
||||
mock_prepare.return_value = {"variant": "Main"}
|
||||
mock_format.side_effect = KeyError("missing_key")
|
||||
with pytest.raises(TemplateFillError):
|
||||
get_product_name(
|
||||
project_name="proj",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="model",
|
||||
variant="Main",
|
||||
)
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.warn")
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
@patch("ayon_core.pipeline.create.product_name."
|
||||
"StringTemplate.format_strict_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.prepare_template_data")
|
||||
def test_warns_when_template_needs_base_type_but_missing(
|
||||
self,
|
||||
mock_prepare,
|
||||
mock_format,
|
||||
mock_get_tmpl,
|
||||
mock_warn,
|
||||
):
|
||||
mock_get_tmpl.return_value = "{product[basetype]}_{variant}"
|
||||
|
||||
mock_prepare.return_value = {
|
||||
"product": {"type": "model"},
|
||||
"variant": "Main",
|
||||
"family": "model",
|
||||
}
|
||||
mock_format.return_value = "asset_Main"
|
||||
|
||||
_ = get_product_name(
|
||||
project_name="proj",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="model",
|
||||
variant="Main",
|
||||
)
|
||||
mock_warn.assert_called_once()
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
@patch("ayon_core.pipeline.create.product_name."
|
||||
"StringTemplate.format_strict_template")
|
||||
@patch("ayon_core.pipeline.create.product_name.prepare_template_data")
|
||||
def test_dynamic_data_overrides_defaults(
|
||||
self, mock_prepare, mock_format, mock_get_tmpl
|
||||
):
|
||||
mock_get_tmpl.return_value = "{custom}_{variant}"
|
||||
mock_prepare.return_value = {"custom": "overridden", "variant": "Main"}
|
||||
mock_format.return_value = "overridden_Main"
|
||||
|
||||
result = get_product_name(
|
||||
project_name="proj",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="model",
|
||||
variant="Main",
|
||||
dynamic_data={"custom": "overridden"},
|
||||
)
|
||||
assert result == "overridden_Main"
|
||||
|
||||
@patch("ayon_core.pipeline.create.product_name.get_product_name_template")
|
||||
def test_product_type_filter_is_used(self, mock_get_tmpl):
|
||||
mock_get_tmpl.return_value = DEFAULT_PRODUCT_TEMPLATE
|
||||
_ = get_product_name(
|
||||
project_name="proj",
|
||||
task_name="modeling",
|
||||
task_type="Modeling",
|
||||
host_name="maya",
|
||||
product_type="model",
|
||||
variant="Main",
|
||||
product_type_filter="look",
|
||||
)
|
||||
args, kwargs = mock_get_tmpl.call_args
|
||||
assert kwargs["product_type"] == "look"
|
||||
Loading…
Add table
Add a link
Reference in a new issue