mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge branch 'develop' into enhancement/1524-yn-0156-usd-contribution-workflow-layer-strength-configured-hierarchically
This commit is contained in:
commit
048cbddb43
2 changed files with 338 additions and 73 deletions
|
|
@ -544,11 +544,6 @@ class BaseCreator(ABC):
|
|||
if host_name is None:
|
||||
host_name = self.create_context.host_name
|
||||
|
||||
task_name = task_type = None
|
||||
if task_entity:
|
||||
task_name = task_entity["name"]
|
||||
task_type = task_entity["taskType"]
|
||||
|
||||
dynamic_data = self.get_dynamic_data(
|
||||
project_name,
|
||||
folder_entity,
|
||||
|
|
@ -564,16 +559,15 @@ class BaseCreator(ABC):
|
|||
|
||||
return get_product_name(
|
||||
project_name,
|
||||
task_name,
|
||||
task_type,
|
||||
host_name,
|
||||
self.product_type,
|
||||
variant,
|
||||
folder_entity=folder_entity,
|
||||
task_entity=task_entity,
|
||||
product_base_type=self.product_base_type,
|
||||
product_type=self.product_type,
|
||||
host_name=host_name,
|
||||
variant=variant,
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
"""Functions for handling product names."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional, Union
|
||||
import warnings
|
||||
from functools import wraps
|
||||
from typing import Any, Optional, Union, overload
|
||||
from warnings import warn
|
||||
|
||||
import ayon_api
|
||||
|
|
@ -9,12 +11,17 @@ from ayon_core.lib import (
|
|||
StringTemplate,
|
||||
filter_profiles,
|
||||
prepare_template_data,
|
||||
Logger,
|
||||
is_func_signature_supported,
|
||||
)
|
||||
from ayon_core.lib.path_templates import TemplateResult
|
||||
from ayon_core.settings import get_project_settings
|
||||
|
||||
from .constants import DEFAULT_PRODUCT_TEMPLATE
|
||||
from .exceptions import TaskNotSetError, TemplateFillError
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
def get_product_name_template(
|
||||
project_name: str,
|
||||
|
|
@ -82,7 +89,7 @@ def get_product_name_template(
|
|||
return template
|
||||
|
||||
|
||||
def get_product_name(
|
||||
def _get_product_name_old(
|
||||
project_name: str,
|
||||
task_name: Optional[str],
|
||||
task_type: Optional[str],
|
||||
|
|
@ -94,61 +101,16 @@ def get_product_name(
|
|||
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.
|
||||
|
||||
Subst name templates are defined in `project_settings/global/tools/creator
|
||||
/product_name_profiles` where are profiles with host name, product type,
|
||||
task name and task type filters. If context does not match any profile
|
||||
then `DEFAULT_PRODUCT_TEMPLATE` is used as default template.
|
||||
|
||||
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 (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.
|
||||
default_template (Optional[str]): Default template if any profile does
|
||||
not match passed context. Constant 'DEFAULT_PRODUCT_TEMPLATE'
|
||||
is used if is not passed.
|
||||
dynamic_data (Optional[Dict[str, Any]]): Dynamic data specific for
|
||||
a creator which creates instance.
|
||||
project_settings (Optional[Union[Dict[str, Any]]]): Prepared settings
|
||||
for project. Settings are queried if not passed.
|
||||
product_type_filter (Optional[str]): Use different product type for
|
||||
product template filtering. Value of `product_type` is used when
|
||||
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.
|
||||
|
||||
Raises:
|
||||
TaskNotSetError: If template requires task which is not provided.
|
||||
TemplateFillError: If filled template contains placeholder key which
|
||||
is not collected.
|
||||
|
||||
"""
|
||||
product_base_type: Optional[str] = None,
|
||||
) -> TemplateResult:
|
||||
warnings.warn(
|
||||
"Used deprecated 'task_name' and 'task_type' arguments."
|
||||
" Please use new signature with 'folder_entity' and 'task_entity'.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
if not product_type:
|
||||
return ""
|
||||
return StringTemplate("").format({})
|
||||
|
||||
template = get_product_name_template(
|
||||
project_name=project_name,
|
||||
|
|
@ -160,19 +122,26 @@ def get_product_name(
|
|||
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
|
||||
if not task_name and "{task" in template.lower():
|
||||
|
||||
template_low = template.lower()
|
||||
# Simple check of task name existence for template with {task[name]} in
|
||||
if not task_name and "{task" in template_low:
|
||||
raise TaskNotSetError()
|
||||
|
||||
task_value = {
|
||||
"name": task_name,
|
||||
"type": task_type,
|
||||
}
|
||||
if "{task}" in template.lower():
|
||||
if "{task}" in template_low:
|
||||
task_value = task_name
|
||||
# NOTE this is message for TDs and Admins -> not really for users
|
||||
# TODO validate this in settings and not allow it
|
||||
log.warning(
|
||||
"Found deprecated task key '{task}' in product name template."
|
||||
" Please use '{task[name]}' instead."
|
||||
)
|
||||
|
||||
elif "{task[short]}" in template.lower():
|
||||
elif "{task[short]}" in template_low:
|
||||
if project_entity is None:
|
||||
project_entity = ayon_api.get_project(project_name)
|
||||
task_types_by_name = {
|
||||
|
|
@ -217,3 +186,305 @@ def get_product_name(
|
|||
f" Available values are {fill_pairs}"
|
||||
)
|
||||
raise TemplateFillError(msg) from exp
|
||||
|
||||
|
||||
def _backwards_compatibility_product_name(func):
|
||||
"""Helper to decide which variant of 'get_product_name' to use.
|
||||
|
||||
The old version expected 'task_name' and 'task_type' arguments. The new
|
||||
version expects 'folder_entity' and 'task_entity' arguments instead.
|
||||
|
||||
The function is also marked with an attribute 'version' so other addons
|
||||
can check if the function is using the new signature or is using
|
||||
the old signature. That should allow addons to adapt to new signature.
|
||||
>>> if getattr(get_product_name, "use_entities", None):
|
||||
>>> # New signature is used
|
||||
>>> path = get_product_name(project_name, folder_entity, ...)
|
||||
>>> else:
|
||||
>>> # Old signature is used
|
||||
>>> path = get_product_name(project_name, taks_name, ...)
|
||||
"""
|
||||
# Add attribute to function to identify it as the new function
|
||||
# so other addons can easily identify it.
|
||||
# >>> geattr(get_product_name, "use_entities", False)
|
||||
setattr(func, "use_entities", True)
|
||||
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
# ---
|
||||
# Decide which variant of the function is used based on
|
||||
# passed arguments.
|
||||
# ---
|
||||
|
||||
# Entities in key-word arguments mean that the new function is used
|
||||
if "folder_entity" in kwargs or "task_entity" in kwargs:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# Using more than 7 positional arguments is not allowed
|
||||
# in the new function
|
||||
if len(args) > 7:
|
||||
return _get_product_name_old(*args, **kwargs)
|
||||
|
||||
if len(args) > 1:
|
||||
arg_2 = args[1]
|
||||
# The second argument is a string -> task name
|
||||
if isinstance(arg_2, str):
|
||||
return _get_product_name_old(*args, **kwargs)
|
||||
|
||||
if is_func_signature_supported(func, *args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return _get_product_name_old(*args, **kwargs)
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@overload
|
||||
def get_product_name(
|
||||
project_name: str,
|
||||
folder_entity: dict[str, Any],
|
||||
task_entity: Optional[dict[str, Any]],
|
||||
product_base_type: str,
|
||||
product_type: str,
|
||||
host_name: str,
|
||||
variant: str,
|
||||
*,
|
||||
dynamic_data: Optional[dict[str, Any]] = None,
|
||||
project_settings: Optional[dict[str, Any]] = None,
|
||||
project_entity: Optional[dict[str, Any]] = None,
|
||||
default_template: Optional[str] = None,
|
||||
product_base_type_filter: Optional[str] = None,
|
||||
) -> TemplateResult:
|
||||
"""Calculate product name based on passed context and AYON settings.
|
||||
|
||||
Subst name templates are defined in `project_settings/global/tools/creator
|
||||
/product_name_profiles` where are profiles with host name, product type,
|
||||
task name and task type filters. If context does not match any profile
|
||||
then `DEFAULT_PRODUCT_TEMPLATE` is used as default template.
|
||||
|
||||
That's main reason why so many arguments are required to calculate product
|
||||
name.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
folder_entity (Optional[dict[str, Any]]): Folder entity.
|
||||
task_entity (Optional[dict[str, Any]]): Task entity.
|
||||
host_name (str): Host name.
|
||||
product_base_type (str): Product base type.
|
||||
product_type (str): Product type.
|
||||
variant (str): In most of the cases it is user input during creation.
|
||||
dynamic_data (Optional[dict[str, Any]]): Dynamic data specific for
|
||||
a creator which creates instance.
|
||||
project_settings (Optional[dict[str, Any]]): Prepared settings
|
||||
for project. Settings are queried if not passed.
|
||||
project_entity (Optional[dict[str, Any]]): Project entity used when
|
||||
task short name is required by template.
|
||||
default_template (Optional[str]): Default template if any profile does
|
||||
not match passed context. Constant 'DEFAULT_PRODUCT_TEMPLATE'
|
||||
is used if is not passed.
|
||||
product_base_type_filter (Optional[str]): Use different product base
|
||||
type for product template filtering. Value of
|
||||
`product_base_type_filter` is used when not passed.
|
||||
|
||||
Returns:
|
||||
TemplateResult: Product name.
|
||||
|
||||
Raises:
|
||||
TaskNotSetError: If template requires task which is not provided.
|
||||
TemplateFillError: If filled template contains placeholder key which
|
||||
is not collected.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@overload
|
||||
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,
|
||||
) -> TemplateResult:
|
||||
"""Calculate product name based on passed context and AYON settings.
|
||||
|
||||
Product name templates are defined in `project_settings/global/tools
|
||||
/creator/product_name_profiles` where are profiles with host name,
|
||||
product type, task name and task type filters. If context does not match
|
||||
any profile then `DEFAULT_PRODUCT_TEMPLATE` is used as default template.
|
||||
|
||||
That's main reason why so many arguments are required to calculate product
|
||||
name.
|
||||
|
||||
Deprecated:
|
||||
This function is using deprecated signature that does not support
|
||||
folder entity data to be used.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
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.
|
||||
default_template (Optional[str]): Default template if any profile does
|
||||
not match passed context. Constant 'DEFAULT_PRODUCT_TEMPLATE'
|
||||
is used if is not passed.
|
||||
dynamic_data (Optional[Dict[str, Any]]): Dynamic data specific for
|
||||
a creator which creates instance.
|
||||
project_settings (Optional[Union[Dict[str, Any]]]): Prepared settings
|
||||
for project. Settings are queried if not passed.
|
||||
product_type_filter (Optional[str]): Use different product type for
|
||||
product template filtering. Value of `product_type` is used when
|
||||
not passed.
|
||||
project_entity (Optional[Dict[str, Any]]): Project entity used when
|
||||
task short name is required by template.
|
||||
|
||||
Returns:
|
||||
TemplateResult: Product name.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@_backwards_compatibility_product_name
|
||||
def get_product_name(
|
||||
project_name: str,
|
||||
folder_entity: dict[str, Any],
|
||||
task_entity: Optional[dict[str, Any]],
|
||||
product_base_type: str,
|
||||
product_type: str,
|
||||
host_name: str,
|
||||
variant: str,
|
||||
*,
|
||||
dynamic_data: Optional[dict[str, Any]] = None,
|
||||
project_settings: Optional[dict[str, Any]] = None,
|
||||
project_entity: Optional[dict[str, Any]] = None,
|
||||
default_template: Optional[str] = None,
|
||||
product_base_type_filter: Optional[str] = None,
|
||||
) -> TemplateResult:
|
||||
"""Calculate product name based on passed context and AYON settings.
|
||||
|
||||
Product name templates are defined in `project_settings/global/tools
|
||||
/creator/product_name_profiles` where are profiles with host name,
|
||||
product base type, product type, task name and task type filters.
|
||||
|
||||
If context does not match any profile then `DEFAULT_PRODUCT_TEMPLATE`
|
||||
is used as default template.
|
||||
|
||||
That's main reason why so many arguments are required to calculate product
|
||||
name.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
folder_entity (Optional[dict[str, Any]]): Folder entity.
|
||||
task_entity (Optional[dict[str, Any]]): Task entity.
|
||||
host_name (str): Host name.
|
||||
product_base_type (str): Product base type.
|
||||
product_type (str): Product type.
|
||||
variant (str): In most of the cases it is user input during creation.
|
||||
dynamic_data (Optional[dict[str, Any]]): Dynamic data specific for
|
||||
a creator which creates instance.
|
||||
project_settings (Optional[dict[str, Any]]): Prepared settings
|
||||
for project. Settings are queried if not passed.
|
||||
project_entity (Optional[dict[str, Any]]): Project entity used when
|
||||
task short name is required by template.
|
||||
default_template (Optional[str]): Default template if any profile does
|
||||
not match passed context. Constant 'DEFAULT_PRODUCT_TEMPLATE'
|
||||
is used if is not passed.
|
||||
product_base_type_filter (Optional[str]): Use different product base
|
||||
type for product template filtering. Value of
|
||||
`product_base_type_filter` is used when not passed.
|
||||
|
||||
Returns:
|
||||
TemplateResult: Product name.
|
||||
|
||||
Raises:
|
||||
TaskNotSetError: If template requires task which is not provided.
|
||||
TemplateFillError: If filled template contains placeholder key which
|
||||
is not collected.
|
||||
|
||||
"""
|
||||
if not product_type:
|
||||
return StringTemplate("").format({})
|
||||
|
||||
task_name = task_type = None
|
||||
if task_entity:
|
||||
task_name = task_entity["name"]
|
||||
task_type = task_entity["taskType"]
|
||||
|
||||
template = get_product_name_template(
|
||||
project_name=project_name,
|
||||
product_base_type=product_base_type_filter or product_base_type,
|
||||
product_type=product_type,
|
||||
task_name=task_name,
|
||||
task_type=task_type,
|
||||
host_name=host_name,
|
||||
default_template=default_template,
|
||||
project_settings=project_settings,
|
||||
)
|
||||
|
||||
template_low = template.lower()
|
||||
# Simple check of task name existence for template with {task[name]} in
|
||||
if not task_name and "{task" in template_low:
|
||||
raise TaskNotSetError()
|
||||
|
||||
task_value = {
|
||||
"name": task_name,
|
||||
"type": task_type,
|
||||
}
|
||||
if "{task}" in template_low:
|
||||
task_value = task_name
|
||||
# NOTE this is message for TDs and Admins -> not really for users
|
||||
# TODO validate this in settings and not allow it
|
||||
log.warning(
|
||||
"Found deprecated task key '{task}' in product name template."
|
||||
" Please use '{task[name]}' instead."
|
||||
)
|
||||
|
||||
elif "{task[short]}" in template_low:
|
||||
if project_entity is None:
|
||||
project_entity = ayon_api.get_project(project_name)
|
||||
task_types_by_name = {
|
||||
task["name"]: task
|
||||
for task in project_entity["taskTypes"]
|
||||
}
|
||||
task_short = task_types_by_name.get(task_type, {}).get("shortName")
|
||||
task_value["short"] = task_short
|
||||
|
||||
fill_pairs = {
|
||||
"variant": variant,
|
||||
# TODO We should stop support 'family' key.
|
||||
"family": product_type,
|
||||
"task": task_value,
|
||||
"product": {
|
||||
"type": product_type,
|
||||
"basetype": product_base_type,
|
||||
}
|
||||
}
|
||||
if folder_entity:
|
||||
fill_pairs["folder"] = {
|
||||
"name": folder_entity["name"],
|
||||
"type": folder_entity["folderType"],
|
||||
}
|
||||
|
||||
if dynamic_data:
|
||||
# Dynamic data may override default values
|
||||
for key, value in dynamic_data.items():
|
||||
fill_pairs[key] = value
|
||||
|
||||
try:
|
||||
return StringTemplate.format_strict_template(
|
||||
template=template,
|
||||
data=prepare_template_data(fill_pairs)
|
||||
)
|
||||
except KeyError as exp:
|
||||
msg = (
|
||||
f"Value for {exp} key is missing in template '{template}'."
|
||||
f" Available values are {fill_pairs}"
|
||||
)
|
||||
raise TemplateFillError(msg)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue