From 07650130c601d9cbb5d3370bc5faaff54333bfbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 15:11:24 +0200 Subject: [PATCH 01/15] initial support to use folder in product name template --- .../ayon_core/pipeline/create/product_name.py | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index ecffa4a340..58cf251f9d 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -1,14 +1,19 @@ +import warnings + import ayon_api from ayon_core.lib import ( StringTemplate, filter_profiles, prepare_template_data, + Logger, ) 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, @@ -81,6 +86,8 @@ def get_product_name( project_settings=None, product_type_filter=None, project_entity=None, + folder_entity=None, + task_entity=None, ): """Calculate product name based on passed context and AYON settings. @@ -98,8 +105,8 @@ def get_product_name( Args: project_name (str): Project name. - task_name (Union[str, None]): Task name. - task_type (Union[str, None]): Task type. + task_name (Union[str, None]): Task name. Deprecated use 'task_entity'. + task_type (Union[str, None]): Task type. Deprecated use 'task_entity'. 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 +122,8 @@ def get_product_name( not passed. project_entity (Optional[Dict[str, Any]]): Project entity used when task short name is required by template. + folder_entity (Optional[Dict[str, Any]]): Folder entity. + task_entity (Optional[Dict[str, Any]]): Task entity. Returns: str: Product name. @@ -139,17 +148,36 @@ def get_product_name( ) # 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(): + if task_name and not task_entity: + warnings.warn( + "Used deprecated 'task' argument. Please use" + " 'task_entity' instead.", + DeprecationWarning, + stacklevel=2 + ) + + if task_entity: + task_name = task_entity["name"] + task_type = task_entity["taskType"] + + template_low = template.lower() + 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 = { @@ -167,6 +195,12 @@ def get_product_name( "type": product_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(): From fc7ca39f39465f5c70be3ac7a3f38b75c2e2967b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 15:13:19 +0200 Subject: [PATCH 02/15] move comment to correct place --- client/ayon_core/pipeline/create/product_name.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 58cf251f9d..d2d161a789 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -146,8 +146,6 @@ def get_product_name( default_template=default_template, project_settings=project_settings ) - # Simple check of task name existence for template with {task} in - # - missing task should be possible only in Standalone publisher if task_name and not task_entity: warnings.warn( "Used deprecated 'task' argument. Please use" @@ -161,6 +159,7 @@ def get_product_name( task_type = task_entity["taskType"] 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() From f7e9f6e7c9f7914d623bdbb874b73f4818f0e7bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 15:17:51 +0200 Subject: [PATCH 03/15] use kwargs in default implementation --- client/ayon_core/pipeline/create/creator_plugins.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 7573589b82..56fa431090 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -566,14 +566,16 @@ 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, + host_name=host_name, + product_type=self.product_type, + variant=variant, dynamic_data=dynamic_data, project_settings=self.project_settings, project_entity=project_entity, + task_name=task_name, + task_type=task_type, ) def get_instance_attr_defs(self): From 348e11f9680bd7c754ac04854c9d162471f48bca Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:40:12 +0200 Subject: [PATCH 04/15] wrap get_product_name function --- .../ayon_core/pipeline/create/product_name.py | 309 +++++++++++++----- 1 file changed, 235 insertions(+), 74 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index d2d161a789..1b22ff4523 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -1,4 +1,8 @@ +from __future__ import annotations + import warnings +from functools import wraps +from typing import Optional, Any import ayon_api from ayon_core.lib import ( @@ -6,7 +10,9 @@ from ayon_core.lib import ( 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 @@ -74,68 +80,27 @@ def get_product_name_template( return 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, - folder_entity=None, - task_entity=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. - - 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. Deprecated use 'task_entity'. - task_type (Union[str, None]): Task type. Deprecated use 'task_entity'. - 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. - folder_entity (Optional[Dict[str, Any]]): Folder entity. - task_entity (Optional[Dict[str, Any]]): Task entity. - - 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. - - """ +def _get_product_name_old( + 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, +) -> 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, @@ -146,17 +111,6 @@ def get_product_name( default_template=default_template, project_settings=project_settings ) - if task_name and not task_entity: - warnings.warn( - "Used deprecated 'task' argument. Please use" - " 'task_entity' instead.", - DeprecationWarning, - stacklevel=2 - ) - - if task_entity: - task_name = task_entity["name"] - task_type = task_entity["taskType"] template_low = template.lower() # Simple check of task name existence for template with {task[name]} in @@ -194,6 +148,106 @@ def get_product_name( "type": product_type } } + + 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: + raise TemplateFillError( + "Value for {} key is missing in template '{}'." + " Available values are {}".format(str(exp), template, fill_pairs) + ) + + +def _get_product_name( + project_name: str, + folder_entity: dict[str, Any], + task_entity: Optional[dict[str, Any]], + 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, + # Ignore unused kwargs passed to 'get_product_name' + task_name: Optional[str] = None, + task_type: Optional[str] = None, +) -> TemplateResult: + """Future replacement of 'get_product_name' function.""" + # Future warning when 'task_name' and 'task_type' are deprecated + # if task_name is None: + # warnings.warn( + # "Still using deprecated 'task_name' argument. Please use" + # " 'task_entity' only.", + # DeprecationWarning, + # stacklevel=2 + # ) + + 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, + product_type_filter or product_type, + task_name, + task_type, + 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 + } + } if folder_entity: fill_pairs["folder"] = { "name": folder_entity["name"], @@ -212,6 +266,113 @@ def get_product_name( ) except KeyError as exp: raise TemplateFillError( - "Value for {} key is missing in template '{}'." - " Available values are {}".format(str(exp), template, fill_pairs) + f"Value for {exp} key is missing in template '{template}'." + f" Available values are {fill_pairs}" ) + + +def _get_product_name_decorator(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. + """ + @wraps(_get_product_name) + 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 6 positional arguments is not allowed + # in the new function + if len(args) > 6: + return func(*args, **kwargs) + + if len(args) > 1: + arg_2 = args[1] + # Second argument is dictionary -> folder entity + if isinstance(arg_2, dict): + return func(*args, **kwargs) + + if is_func_signature_supported(func, *args, **kwargs): + return func(*args, **kwargs) + return _get_product_name_old(*args, **kwargs) + + return inner + + +def get_product_name( + project_name: str, + folder_entity: dict[str, Any], + task_entity: Optional[dict[str, Any]], + 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, +) -> 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. + + Todos: + Find better filtering options to avoid requirement of + argument 'family_filter'. + + 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_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. + + Raises: + TaskNotSetError: If template requires task which is not provided. + TemplateFillError: If filled template contains placeholder key which + is not collected. + + """ + return _get_product_name( + project_name, + folder_entity, + task_entity, + host_name, + product_type, + variant, + default_template=default_template, + dynamic_data=dynamic_data, + project_settings=project_settings, + product_type_filter=product_type_filter, + project_entity=project_entity, + ) From 31b023b0fac2452af6bd3bc78d977d03ec802441 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:47:14 +0200 Subject: [PATCH 05/15] use only new signature --- client/ayon_core/pipeline/create/creator_plugins.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 56fa431090..931b33afd4 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -546,11 +546,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, @@ -574,8 +569,6 @@ class BaseCreator(ABC): dynamic_data=dynamic_data, project_settings=self.project_settings, project_entity=project_entity, - task_name=task_name, - task_type=task_type, ) def get_instance_attr_defs(self): From 16b45846094c5616f90412d7c4130f3767839d59 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:50:57 +0200 Subject: [PATCH 06/15] mark the function with an attribute to know if entities are expected in arguments --- client/ayon_core/pipeline/create/product_name.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 1b22ff4523..f4ec4199d5 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -277,6 +277,11 @@ def _get_product_name_decorator(func): The old version expected 'task_name' and 'task_type' arguments. The new version expects 'folder_entity' and 'task_entity' arguments instead. """ + # 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) + func.use_entities = True + @wraps(_get_product_name) def inner(*args, **kwargs): # --- From a35b179ed1122f435bb5c83b509c0f957f2a4bcf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:59:46 +0200 Subject: [PATCH 07/15] remove the private variant of the function --- .../ayon_core/pipeline/create/product_name.py | 209 +++++++----------- 1 file changed, 84 insertions(+), 125 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index f4ec4199d5..687d152e89 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -166,7 +166,48 @@ def _get_product_name_old( ) -def _get_product_name( +def _get_product_name_decorator(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. + """ + # 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) + 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 6 positional arguments is not allowed + # in the new function + if len(args) > 6: + 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 + + +@_get_product_name_decorator +def get_product_name( project_name: str, folder_entity: dict[str, Any], task_entity: Optional[dict[str, Any]], @@ -179,20 +220,50 @@ def _get_product_name( project_settings: Optional[dict[str, Any]] = None, product_type_filter: Optional[str] = None, project_entity: Optional[dict[str, Any]] = None, - # Ignore unused kwargs passed to 'get_product_name' - task_name: Optional[str] = None, - task_type: Optional[str] = None, ) -> TemplateResult: - """Future replacement of 'get_product_name' function.""" - # Future warning when 'task_name' and 'task_type' are deprecated - # if task_name is None: - # warnings.warn( - # "Still using deprecated 'task_name' argument. Please use" - # " 'task_entity' only.", - # DeprecationWarning, - # stacklevel=2 - # ) + """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. + + Todos: + Find better filtering options to avoid requirement of + argument 'family_filter'. + + 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_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. + + 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({}) @@ -269,115 +340,3 @@ def _get_product_name( f"Value for {exp} key is missing in template '{template}'." f" Available values are {fill_pairs}" ) - - -def _get_product_name_decorator(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. - """ - # 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) - func.use_entities = True - - @wraps(_get_product_name) - 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 6 positional arguments is not allowed - # in the new function - if len(args) > 6: - return func(*args, **kwargs) - - if len(args) > 1: - arg_2 = args[1] - # Second argument is dictionary -> folder entity - if isinstance(arg_2, dict): - return func(*args, **kwargs) - - if is_func_signature_supported(func, *args, **kwargs): - return func(*args, **kwargs) - return _get_product_name_old(*args, **kwargs) - - return inner - - -def get_product_name( - project_name: str, - folder_entity: dict[str, Any], - task_entity: Optional[dict[str, Any]], - 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, -) -> 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. - - Todos: - Find better filtering options to avoid requirement of - argument 'family_filter'. - - 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_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. - - Raises: - TaskNotSetError: If template requires task which is not provided. - TemplateFillError: If filled template contains placeholder key which - is not collected. - - """ - return _get_product_name( - project_name, - folder_entity, - task_entity, - host_name, - product_type, - variant, - default_template=default_template, - dynamic_data=dynamic_data, - project_settings=project_settings, - product_type_filter=product_type_filter, - project_entity=project_entity, - ) From 5fd5b73e913eb45b2810b3ba7d63531d20758362 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:05:19 +0200 Subject: [PATCH 08/15] fix type hints --- client/ayon_core/pipeline/create/product_name.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 687d152e89..ede3141537 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -39,10 +39,10 @@ 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 (Optional[dict[str, Any]]): Prepared settings for project. Settings are queried if not passed. """ @@ -237,22 +237,22 @@ def get_product_name( Args: project_name (str): Project name. - folder_entity (Optional[Dict[str, Any]]): Folder entity. - task_entity (Optional[Dict[str, Any]]): Task entity. + folder_entity (Optional[dict[str, Any]]): Folder entity. + task_entity (Optional[dict[str, Any]]): Task entity. 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 + dynamic_data (Optional[dict[str, Any]]): Dynamic data specific for a creator which creates instance. - project_settings (Optional[Union[Dict[str, Any]]]): Prepared settings + project_settings (Optional[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 + project_entity (Optional[dict[str, Any]]): Project entity used when task short name is required by template. Returns: From 882c0bcc6aed066026e67bfe1b4c211038c08576 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:58:26 +0200 Subject: [PATCH 09/15] rename decorator and add more information to the example --- client/ayon_core/pipeline/create/product_name.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index ede3141537..45b77d1a95 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -166,11 +166,21 @@ def _get_product_name_old( ) -def _get_product_name_decorator(func): +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. @@ -206,7 +216,7 @@ def _get_product_name_decorator(func): return inner -@_get_product_name_decorator +@_backwards_compatibility_product_name def get_product_name( project_name: str, folder_entity: dict[str, Any], From d7433f84d796abb04d0a0aed5fa3eee134ecaf02 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:58:34 +0200 Subject: [PATCH 10/15] use setattr --- client/ayon_core/pipeline/create/product_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 45b77d1a95..ee07f939bc 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -185,7 +185,7 @@ def _backwards_compatibility_product_name(func): # 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) - func.use_entities = True + setattr(func, "use_entities", True) @wraps(func) def inner(*args, **kwargs): From d6431a49908f3bc5bd14b39f2c0c18ce6f7e3137 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:17:13 +0100 Subject: [PATCH 11/15] added overload functionality --- .../ayon_core/pipeline/create/product_name.py | 117 +++++++++++++++++- 1 file changed, 112 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index ee07f939bc..a85b12f0df 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -2,7 +2,7 @@ from __future__ import annotations import warnings from functools import wraps -from typing import Optional, Any +from typing import Optional, Any, overload import ayon_api from ayon_core.lib import ( @@ -216,7 +216,7 @@ def _backwards_compatibility_product_name(func): return inner -@_backwards_compatibility_product_name +@overload def get_product_name( project_name: str, folder_entity: dict[str, Any], @@ -241,9 +241,116 @@ def get_product_name( That's main reason why so many arguments are required to calculate product name. - Todos: - Find better filtering options to avoid requirement of - argument 'family_filter'. + 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_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[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. + + 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 deprecate 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]], + 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, +) -> 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. Args: project_name (str): Project name. From cf28f96eda987207bdaa6161d2cfd19d46aad264 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:49:11 +0100 Subject: [PATCH 12/15] fix formatting in docstring --- client/ayon_core/pipeline/create/product_name.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index b0bb2d3430..89ae7ef85b 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -367,13 +367,15 @@ def get_product_name( ) -> 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. + 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. + name. Args: project_name (str): Project name. From d4e5f96b3b49d7a0e2bf2041d67657e24005a392 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:46:48 +0100 Subject: [PATCH 13/15] upodate overload function --- .../ayon_core/pipeline/create/product_name.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 89ae7ef85b..2b1255c2b3 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -243,15 +243,16 @@ def get_product_name( project_name: str, folder_entity: dict[str, Any], task_entity: Optional[dict[str, Any]], - host_name: str, + product_base_type: str, product_type: str, + host_name: 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, + default_template: Optional[str] = None, + product_base_type_filter: Optional[str] = None, ) -> TemplateResult: """Calculate product name based on passed context and AYON settings. @@ -268,20 +269,21 @@ def get_product_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. - 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[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. + 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. From bceb645a80da8a2f671d42e2a8d6b5feaea42b5a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:47:14 +0100 Subject: [PATCH 14/15] fix typo Co-authored-by: Roy Nieterau --- client/ayon_core/pipeline/create/product_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 2b1255c2b3..a0bfc18eba 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -321,7 +321,7 @@ def get_product_name( name. Deprecated: - This function is using deprecate signature that does not support + This function is using deprecated signature that does not support folder entity data to be used. Args: From 17b09d608bfc33b6bfa7904adfb9abbf3d7b3df8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:03:30 +0100 Subject: [PATCH 15/15] unify indentation --- client/ayon_core/pipeline/create/product_name.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index a0bfc18eba..d32de54774 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -370,8 +370,8 @@ def get_product_name( """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. + /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.