From 80ba7ea5ed209d3b3eb0f51cb2025ae046c3ec87 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:12:30 +0200 Subject: [PATCH 01/16] implement new 'get_representation_path_v2' function --- client/ayon_core/pipeline/load/__init__.py | 2 + client/ayon_core/pipeline/load/utils.py | 56 ++++++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/load/__init__.py b/client/ayon_core/pipeline/load/__init__.py index 2a33fa119b..9ad2a17d3d 100644 --- a/client/ayon_core/pipeline/load/__init__.py +++ b/client/ayon_core/pipeline/load/__init__.py @@ -25,6 +25,7 @@ from .utils import ( get_loader_identifier, get_loaders_by_name, + get_representation_path_v2, get_representation_path_from_context, get_representation_path, get_representation_path_with_anatomy, @@ -85,6 +86,7 @@ __all__ = ( "get_loader_identifier", "get_loaders_by_name", + "get_representation_path_v2", "get_representation_path_from_context", "get_representation_path", "get_representation_path_with_anatomy", diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index d1731d4cf9..7842741c85 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -14,9 +14,8 @@ from ayon_core.lib import ( StringTemplate, TemplateUnsolved, ) -from ayon_core.pipeline import ( - Anatomy, -) +from ayon_core.lib.path_templates import TemplateResult +from ayon_core.pipeline import Anatomy log = logging.getLogger(__name__) @@ -638,6 +637,57 @@ def _fix_representation_context_compatibility(repre_context): repre_context["udim"] = udim[0] +def get_representation_path_v2( + project_name: str, + repre_entity: dict[str, Any], + *, + anatomy: Optional[Anatomy] = None, + project_entity: Optional[dict[str, Any]] = None, +) -> TemplateResult: + """Get filled representation path. + + Args: + project_name (str): Project name. + repre_entity (dict[str, Any]): Representation entity. + anatomy (Optional[Anatomy]): Project anatomy. + project_entity (Optional[dict[str, Any]): Project entity. Is used to + initialize Anatomy and is not needed if 'anatomy' is passed in. + + Returns: + TemplateResult: Resolved path to representation. + + Raises: + InvalidRepresentationContext: When representation data are probably + invalid or not available. + + """ + if anatomy is None: + anatomy = Anatomy(project_name, project_entity=project_entity) + try: + template = repre_entity["attrib"]["template"] + + except KeyError: + raise InvalidRepresentationContext( + "Representation document does not" + " contain template in data ('data.template')" + ) + + try: + context = copy.deepcopy(repre_entity["context"]) + _fix_representation_context_compatibility(context) + context["root"] = anatomy.roots + + path = StringTemplate.format_strict_template(template, context) + + except TemplateUnsolved as exc: + raise InvalidRepresentationContext( + "Couldn't resolve representation template with available data." + f" Reason: {str(exc)}" + ) + + return path.normalized() + + def get_representation_path_from_context(context): """Preparation wrapper using only context as a argument""" from ayon_core.pipeline import get_current_project_name From 60ff1ddb0c0b095114ba2630c7b72ca237e189f9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:14:21 +0200 Subject: [PATCH 02/16] use the new function --- .../pipeline/farm/pyblish_functions.py | 6 +- client/ayon_core/pipeline/load/utils.py | 104 ++++++++---------- .../extract_usd_layer_contributions.py | 3 + 3 files changed, 52 insertions(+), 61 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 0d8e70f9d2..1c8925d290 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -11,7 +11,7 @@ import clique from ayon_core.lib import Logger from ayon_core.pipeline import ( get_current_project_name, - get_representation_path, + get_representation_path_v2, ) from ayon_core.pipeline.create import get_product_name from ayon_core.pipeline.farm.patterning import match_aov_pattern @@ -1044,7 +1044,9 @@ def get_resources(project_name, version_entity, extension=None): filtered.append(repre_entity) representation = filtered[0] - directory = get_representation_path(representation) + directory = get_representation_path_v2( + project_name, representation + ) print("Source: ", directory) resources = sorted( [ diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 7842741c85..267a8c80c7 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import uuid import platform @@ -5,6 +7,7 @@ import logging import inspect import collections import numbers +import copy from typing import Optional, Union, Any import ayon_api @@ -694,15 +697,15 @@ def get_representation_path_from_context(context): representation = context["representation"] project_entity = context.get("project") - root = None - if ( - project_entity - and project_entity["name"] != get_current_project_name() - ): - anatomy = Anatomy(project_entity["name"]) - root = anatomy.roots - - return get_representation_path(representation, root) + if project_entity: + project_name = project_entity["name"] + else: + project_name = get_current_project_name() + return get_representation_path_v2( + project_name, + representation, + project_entity=project_entity, + ) def get_representation_path_with_anatomy(repre_entity, anatomy): @@ -721,36 +724,18 @@ def get_representation_path_with_anatomy(repre_entity, anatomy): anatomy (Anatomy): Project anatomy object. Returns: - Union[None, TemplateResult]: None if path can't be received + TemplateResult: Resolved representation path. Raises: InvalidRepresentationContext: When representation data are probably invalid or not available. + """ - - try: - template = repre_entity["attrib"]["template"] - - except KeyError: - raise InvalidRepresentationContext(( - "Representation document does not" - " contain template in data ('data.template')" - )) - - try: - context = repre_entity["context"] - _fix_representation_context_compatibility(context) - context["root"] = anatomy.roots - - path = StringTemplate.format_strict_template(template, context) - - except TemplateUnsolved as exc: - raise InvalidRepresentationContext(( - "Couldn't resolve representation template with available data." - " Reason: {}".format(str(exc)) - )) - - return path.normalized() + return get_representation_path_v2( + anatomy.project_name, + repre_entity, + anatomy=anatomy, + ) def get_representation_path(representation, root=None): @@ -771,11 +756,12 @@ def get_representation_path(representation, root=None): """ if root is None: - from ayon_core.pipeline import get_current_project_name, Anatomy + from ayon_core.pipeline import get_current_project_name - anatomy = Anatomy(get_current_project_name()) - return get_representation_path_with_anatomy( - representation, anatomy + project_name = get_current_project_name() + return get_representation_path_v2( + project_name, + representation, ) def path_from_representation(): @@ -848,12 +834,13 @@ def get_representation_path(representation, root=None): def get_representation_path_by_names( - project_name: str, - folder_path: str, - product_name: str, - version_name: str, - representation_name: str, - anatomy: Optional[Anatomy] = None) -> Optional[str]: + project_name: str, + folder_path: str, + product_name: str, + version_name: str, + representation_name: str, + anatomy: Optional[Anatomy] = None +) -> Optional[TemplateResult]: """Get (latest) filepath for representation for folder and product. See `get_representation_by_names` for more details. @@ -870,22 +857,21 @@ def get_representation_path_by_names( representation_name ) if not representation: - return + return None - if not anatomy: - anatomy = Anatomy(project_name) - - if representation: - path = get_representation_path_with_anatomy(representation, anatomy) - return str(path).replace("\\", "/") + return get_representation_path_v2( + project_name, + representation, + anatomy=anatomy, + ) def get_representation_by_names( - project_name: str, - folder_path: str, - product_name: str, - version_name: Union[int, str], - representation_name: str, + project_name: str, + folder_path: str, + product_name: str, + version_name: Union[int, str], + representation_name: str, ) -> Optional[dict]: """Get representation entity for asset and subset. @@ -902,7 +888,7 @@ def get_representation_by_names( folder_entity = ayon_api.get_folder_by_path( project_name, folder_path, fields=["id"]) if not folder_entity: - return + return None if isinstance(product_name, dict) and "name" in product_name: # Allow explicitly passing subset document @@ -914,7 +900,7 @@ def get_representation_by_names( folder_id=folder_entity["id"], fields=["id"]) if not product_entity: - return + return None if version_name == "hero": version_entity = ayon_api.get_hero_version_by_product_id( @@ -926,7 +912,7 @@ def get_representation_by_names( version_entity = ayon_api.get_version_by_name( project_name, version_name, product_id=product_entity["id"]) if not version_entity: - return + return None return ayon_api.get_representation_by_name( project_name, representation_name, version_id=version_entity["id"]) diff --git a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py index 0dc9a5e34d..9db8c49a02 100644 --- a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py +++ b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py @@ -1,6 +1,7 @@ from operator import attrgetter import dataclasses import os +import platform from typing import Any, Dict, List import pyblish.api @@ -179,6 +180,8 @@ def get_instance_uri_path( # Ensure `None` for now is also a string path = str(path) + if platform.system().lower() == "windows": + path = path.replace("\\", "/") return path From d55ac4aa547d9f24f96b33a0c66ddf87f4c1186d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:34:47 +0200 Subject: [PATCH 03/16] Use 'get_representation_path' for both signatures. --- .../pipeline/farm/pyblish_functions.py | 4 +- client/ayon_core/pipeline/load/__init__.py | 4 +- client/ayon_core/pipeline/load/utils.py | 240 ++++++++---------- 3 files changed, 115 insertions(+), 133 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 1c8925d290..0dda91914c 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -11,7 +11,7 @@ import clique from ayon_core.lib import Logger from ayon_core.pipeline import ( get_current_project_name, - get_representation_path_v2, + get_representation_path, ) from ayon_core.pipeline.create import get_product_name from ayon_core.pipeline.farm.patterning import match_aov_pattern @@ -1044,7 +1044,7 @@ def get_resources(project_name, version_entity, extension=None): filtered.append(repre_entity) representation = filtered[0] - directory = get_representation_path_v2( + directory = get_representation_path( project_name, representation ) print("Source: ", directory) diff --git a/client/ayon_core/pipeline/load/__init__.py b/client/ayon_core/pipeline/load/__init__.py index 9ad2a17d3d..3bc8d4ba1f 100644 --- a/client/ayon_core/pipeline/load/__init__.py +++ b/client/ayon_core/pipeline/load/__init__.py @@ -25,7 +25,7 @@ from .utils import ( get_loader_identifier, get_loaders_by_name, - get_representation_path_v2, + get_representation_path, get_representation_path_from_context, get_representation_path, get_representation_path_with_anatomy, @@ -86,7 +86,7 @@ __all__ = ( "get_loader_identifier", "get_loaders_by_name", - "get_representation_path_v2", + "get_representation_path", "get_representation_path_from_context", "get_representation_path", "get_representation_path_with_anatomy", diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 267a8c80c7..7cf96a5409 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -2,12 +2,13 @@ from __future__ import annotations import os import uuid -import platform +import warnings import logging import inspect import collections import numbers import copy +from functools import wraps from typing import Optional, Union, Any import ayon_api @@ -640,57 +641,6 @@ def _fix_representation_context_compatibility(repre_context): repre_context["udim"] = udim[0] -def get_representation_path_v2( - project_name: str, - repre_entity: dict[str, Any], - *, - anatomy: Optional[Anatomy] = None, - project_entity: Optional[dict[str, Any]] = None, -) -> TemplateResult: - """Get filled representation path. - - Args: - project_name (str): Project name. - repre_entity (dict[str, Any]): Representation entity. - anatomy (Optional[Anatomy]): Project anatomy. - project_entity (Optional[dict[str, Any]): Project entity. Is used to - initialize Anatomy and is not needed if 'anatomy' is passed in. - - Returns: - TemplateResult: Resolved path to representation. - - Raises: - InvalidRepresentationContext: When representation data are probably - invalid or not available. - - """ - if anatomy is None: - anatomy = Anatomy(project_name, project_entity=project_entity) - try: - template = repre_entity["attrib"]["template"] - - except KeyError: - raise InvalidRepresentationContext( - "Representation document does not" - " contain template in data ('data.template')" - ) - - try: - context = copy.deepcopy(repre_entity["context"]) - _fix_representation_context_compatibility(context) - context["root"] = anatomy.roots - - path = StringTemplate.format_strict_template(template, context) - - except TemplateUnsolved as exc: - raise InvalidRepresentationContext( - "Couldn't resolve representation template with available data." - f" Reason: {str(exc)}" - ) - - return path.normalized() - - def get_representation_path_from_context(context): """Preparation wrapper using only context as a argument""" from ayon_core.pipeline import get_current_project_name @@ -701,7 +651,7 @@ def get_representation_path_from_context(context): project_name = project_entity["name"] else: project_name = get_current_project_name() - return get_representation_path_v2( + return _get_representation_path( project_name, representation, project_entity=project_entity, @@ -731,106 +681,138 @@ def get_representation_path_with_anatomy(repre_entity, anatomy): invalid or not available. """ - return get_representation_path_v2( + return get_representation_path( anatomy.project_name, repre_entity, anatomy=anatomy, ) -def get_representation_path(representation, root=None): - """Get filename from representation document - - There are three ways of getting the path from representation which are - tried in following sequence until successful. - 1. Get template from representation['data']['template'] and data from - representation['context']. Then format template with the data. - 2. Get template from project['config'] and format it with default data set - 3. Get representation['data']['path'] and use it directly +def get_representation_path_with_roots( + representation: dict[str, Any], + roots: dict[str, str], +) -> Optional[TemplateResult]: + """Get filename from representation with custom root. Args: - representation(dict): representation document from the database + representation(dict): Representation entity. + roots (dict[str, str]): Roots to use. + Returns: - str: fullpath of the representation + Optional[TemplateResult]: Resolved representation path. """ - if root is None: - from ayon_core.pipeline import get_current_project_name + try: + template = representation["attrib"]["template"] + except KeyError: + return None - project_name = get_current_project_name() - return get_representation_path_v2( - project_name, - representation, + try: + context = representation["context"] + + _fix_representation_context_compatibility(context) + + context["root"] = roots + path = StringTemplate.format_strict_template( + template, context + ) + except (TemplateUnsolved, KeyError): + # Template references unavailable data + return None + + return path.normalized() + + +def _get_representation_path( + project_name: str, + repre_entity: dict[str, Any], + *, + anatomy: Optional[Anatomy] = None, + project_entity: Optional[dict[str, Any]] = None, +) -> TemplateResult: + """Get filled representation path. + + Args: + project_name (str): Project name. + repre_entity (dict[str, Any]): Representation entity. + anatomy (Optional[Anatomy]): Project anatomy. + project_entity (Optional[dict[str, Any]): Project entity. Is used to + initialize Anatomy and is not needed if 'anatomy' is passed in. + + Returns: + TemplateResult: Resolved path to representation. + + Raises: + InvalidRepresentationContext: When representation data are probably + invalid or not available. + + """ + if anatomy is None: + anatomy = Anatomy(project_name, project_entity=project_entity) + + try: + template = repre_entity["attrib"]["template"] + + except KeyError: + raise InvalidRepresentationContext( + "Representation document does not" + " contain template in data ('data.template')" ) - def path_from_representation(): - try: - template = representation["attrib"]["template"] - except KeyError: - return None + try: + context = copy.deepcopy(repre_entity["context"]) + _fix_representation_context_compatibility(context) + context["root"] = anatomy.roots - try: - context = representation["context"] + path = StringTemplate.format_strict_template(template, context) - _fix_representation_context_compatibility(context) + except TemplateUnsolved as exc: + raise InvalidRepresentationContext( + "Couldn't resolve representation template with available data." + f" Reason: {str(exc)}" + ) - context["root"] = root - path = StringTemplate.format_strict_template( - template, context - ) - # Force replacing backslashes with forward slashed if not on - # windows - if platform.system().lower() != "windows": - path = path.replace("\\", "/") - except (TemplateUnsolved, KeyError): - # Template references unavailable data - return None + return path.normalized() - if not path: - return path - normalized_path = os.path.normpath(path) - if os.path.exists(normalized_path): - return normalized_path - return path +def _get_representation_path_decorator(func): + @wraps(_get_representation_path) + def inner(arg_1, *args, **kwargs): + if isinstance(arg_1, str): + return _get_representation_path(arg_1, *args, **kwargs) + warnings.warn( + ( + "Used deprecated variant of 'get_representation_path'." + " Please change used arguments signature to follow" + " new definiton." + ), + DeprecationWarning, + stacklevel=2, + ) + return get_representation_path_with_roots(arg_1, *args, **kwargs) + return inner - def path_from_data(): - if "path" not in representation["attrib"]: - return None - path = representation["attrib"]["path"] - # Force replacing backslashes with forward slashed if not on - # windows - if platform.system().lower() != "windows": - path = path.replace("\\", "/") +@_get_representation_path_decorator +def get_representation_path(*args, **kwargs) -> TemplateResult: + """Get filled representation path. - if os.path.exists(path): - return os.path.normpath(path) + Args: + project_name (str): Project name. + repre_entity (dict[str, Any]): Representation entity. + anatomy (Optional[Anatomy]): Project anatomy. + project_entity (Optional[dict[str, Any]): Project entity. Is used to + initialize Anatomy and is not needed if 'anatomy' is passed in. - dir_path, file_name = os.path.split(path) - if not os.path.exists(dir_path): - return None + Returns: + TemplateResult: Resolved path to representation. - base_name, ext = os.path.splitext(file_name) - file_name_items = None - if "#" in base_name: - file_name_items = [part for part in base_name.split("#") if part] - elif "%" in base_name: - file_name_items = base_name.split("%") - - if not file_name_items: - return None - - filename_start = file_name_items[0] - - for _file in os.listdir(dir_path): - if _file.startswith(filename_start) and _file.endswith(ext): - return os.path.normpath(path) - - return ( - path_from_representation() or path_from_data() - ) + Raises: + InvalidRepresentationContext: When representation data are probably + invalid or not available. + """ + pass def get_representation_path_by_names( @@ -859,7 +841,7 @@ def get_representation_path_by_names( if not representation: return None - return get_representation_path_v2( + return _get_representation_path( project_name, representation, anatomy=anatomy, From c9bb43059db7891da4b1cf9c30af8d2f4b5bc46c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:36:56 +0200 Subject: [PATCH 04/16] remove doubled import --- client/ayon_core/pipeline/load/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/pipeline/load/__init__.py b/client/ayon_core/pipeline/load/__init__.py index 3bc8d4ba1f..b5b09a5dc9 100644 --- a/client/ayon_core/pipeline/load/__init__.py +++ b/client/ayon_core/pipeline/load/__init__.py @@ -27,7 +27,6 @@ from .utils import ( get_representation_path, get_representation_path_from_context, - get_representation_path, get_representation_path_with_anatomy, is_compatible_loader, @@ -88,7 +87,6 @@ __all__ = ( "get_representation_path", "get_representation_path_from_context", - "get_representation_path", "get_representation_path_with_anatomy", "is_compatible_loader", From 8c61e655216fca3597808707c4751d6db59e15d2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:52:59 +0200 Subject: [PATCH 05/16] handle backwards compatibility properly --- client/ayon_core/pipeline/load/utils.py | 42 ++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 7cf96a5409..9dcb2e3b43 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -778,9 +778,19 @@ def _get_representation_path( def _get_representation_path_decorator(func): @wraps(_get_representation_path) - def inner(arg_1, *args, **kwargs): - if isinstance(arg_1, str): - return _get_representation_path(arg_1, *args, **kwargs) + def inner(*args, **kwargs): + from ayon_core.pipeline import get_current_project_name + + # Decide which variant of the function based on passed arguments + # will be used. + if args: + arg_1 = args[0] + if isinstance(arg_1, str): + return _get_representation_path(*args, **kwargs) + + elif "project_name" in kwargs: + return _get_representation_path(*args, **kwargs) + warnings.warn( ( "Used deprecated variant of 'get_representation_path'." @@ -790,7 +800,31 @@ def _get_representation_path_decorator(func): DeprecationWarning, stacklevel=2, ) - return get_representation_path_with_roots(arg_1, *args, **kwargs) + + # Find out which arguments were passed + if args: + representation = args[0] + else: + representation = kwargs.get("representation") + + if len(args) > 1: + roots = args[1] + else: + roots = kwargs.get("root") + + if roots is not None: + return get_representation_path_with_roots( + representation, roots + ) + + project_name = ( + representation["context"].get("project", {}).get("name") + ) + if project_name is None: + project_name = get_current_project_name() + + return _get_representation_path(project_name, representation) + return inner From efcd4425b7acf0f46a949668522530fa007ee231 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:53:45 +0200 Subject: [PATCH 06/16] add signature to the original function --- client/ayon_core/pipeline/load/utils.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 9dcb2e3b43..b3582ed0a7 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -829,7 +829,12 @@ def _get_representation_path_decorator(func): @_get_representation_path_decorator -def get_representation_path(*args, **kwargs) -> TemplateResult: +def get_representation_path(project_name: str, + repre_entity: dict[str, Any], + *, + anatomy: Optional[Anatomy] = None, + project_entity: Optional[dict[str, Any]] = None, +) -> TemplateResult: """Get filled representation path. Args: @@ -846,7 +851,12 @@ def get_representation_path(*args, **kwargs) -> TemplateResult: InvalidRepresentationContext: When representation data are probably invalid or not available. """ - pass + return _get_representation_path( + project_name, + repre_entity, + anatomy=anatomy, + project_entity=project_entity, + ) def get_representation_path_by_names( From 80f84e95fc02e8f4b72e077e412213ae5fae4540 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:54:02 +0200 Subject: [PATCH 07/16] add formatting --- client/ayon_core/pipeline/load/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index b3582ed0a7..900740cadc 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -850,6 +850,7 @@ def get_representation_path(project_name: str, Raises: InvalidRepresentationContext: When representation data are probably invalid or not available. + """ return _get_representation_path( project_name, From dcf5db31d042eb9ad1f7325ca51f75107490151c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 25 Sep 2025 12:00:28 +0200 Subject: [PATCH 08/16] formatting fix --- client/ayon_core/pipeline/load/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 900740cadc..1d0a0e54e4 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -829,7 +829,8 @@ def _get_representation_path_decorator(func): @_get_representation_path_decorator -def get_representation_path(project_name: str, +def get_representation_path( + project_name: str, repre_entity: dict[str, Any], *, anatomy: Optional[Anatomy] = None, From 725e0f5a11298e119600b58321d628f19be4779b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:17:47 +0200 Subject: [PATCH 09/16] get rid of private function --- client/ayon_core/pipeline/load/utils.py | 146 +++++++++--------------- 1 file changed, 57 insertions(+), 89 deletions(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 1d0a0e54e4..371ce300ba 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -651,7 +651,7 @@ def get_representation_path_from_context(context): project_name = project_entity["name"] else: project_name = get_current_project_name() - return _get_representation_path( + return get_representation_path( project_name, representation, project_entity=project_entity, @@ -724,7 +724,60 @@ def get_representation_path_with_roots( return path.normalized() -def _get_representation_path( +def _get_representation_path_decorator(func): + @wraps(func) + def inner(*args, **kwargs): + from ayon_core.pipeline import get_current_project_name + + # Decide which variant of the function based on passed arguments + # will be used. + if args: + arg_1 = args[0] + if isinstance(arg_1, str): + return func(*args, **kwargs) + + elif "project_name" in kwargs: + return func(*args, **kwargs) + + warnings.warn( + ( + "Used deprecated variant of 'get_representation_path'." + " Please change used arguments signature to follow" + " new definition." + ), + DeprecationWarning, + stacklevel=2, + ) + + # Find out which arguments were passed + if args: + representation = args[0] + else: + representation = kwargs.get("representation") + + if len(args) > 1: + roots = args[1] + else: + roots = kwargs.get("root") + + if roots is not None: + return get_representation_path_with_roots( + representation, roots + ) + + project_name = ( + representation["context"].get("project", {}).get("name") + ) + if project_name is None: + project_name = get_current_project_name() + + return func(project_name, representation) + + return inner + + +@_get_representation_path_decorator +def get_representation_path( project_name: str, repre_entity: dict[str, Any], *, @@ -743,7 +796,7 @@ def _get_representation_path( Returns: TemplateResult: Resolved path to representation. - Raises: + Raises: InvalidRepresentationContext: When representation data are probably invalid or not available. @@ -776,91 +829,6 @@ def _get_representation_path( return path.normalized() -def _get_representation_path_decorator(func): - @wraps(_get_representation_path) - def inner(*args, **kwargs): - from ayon_core.pipeline import get_current_project_name - - # Decide which variant of the function based on passed arguments - # will be used. - if args: - arg_1 = args[0] - if isinstance(arg_1, str): - return _get_representation_path(*args, **kwargs) - - elif "project_name" in kwargs: - return _get_representation_path(*args, **kwargs) - - warnings.warn( - ( - "Used deprecated variant of 'get_representation_path'." - " Please change used arguments signature to follow" - " new definiton." - ), - DeprecationWarning, - stacklevel=2, - ) - - # Find out which arguments were passed - if args: - representation = args[0] - else: - representation = kwargs.get("representation") - - if len(args) > 1: - roots = args[1] - else: - roots = kwargs.get("root") - - if roots is not None: - return get_representation_path_with_roots( - representation, roots - ) - - project_name = ( - representation["context"].get("project", {}).get("name") - ) - if project_name is None: - project_name = get_current_project_name() - - return _get_representation_path(project_name, representation) - - return inner - - -@_get_representation_path_decorator -def get_representation_path( - project_name: str, - repre_entity: dict[str, Any], - *, - anatomy: Optional[Anatomy] = None, - project_entity: Optional[dict[str, Any]] = None, -) -> TemplateResult: - """Get filled representation path. - - Args: - project_name (str): Project name. - repre_entity (dict[str, Any]): Representation entity. - anatomy (Optional[Anatomy]): Project anatomy. - project_entity (Optional[dict[str, Any]): Project entity. Is used to - initialize Anatomy and is not needed if 'anatomy' is passed in. - - Returns: - TemplateResult: Resolved path to representation. - - Raises: - InvalidRepresentationContext: When representation data are probably - invalid or not available. - - """ - return _get_representation_path( - project_name, - repre_entity, - anatomy=anatomy, - project_entity=project_entity, - ) - - def get_representation_path_by_names( project_name: str, folder_path: str, @@ -887,7 +855,7 @@ def get_representation_path_by_names( if not representation: return None - return _get_representation_path( + return get_representation_path( project_name, representation, anatomy=anatomy, From f11800f1e77fc8537fa4cf31bbb4e26bb2777b44 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:22:32 +0200 Subject: [PATCH 10/16] fix type hint --- client/ayon_core/pipeline/load/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 371ce300ba..225d4e0d44 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -833,7 +833,7 @@ def get_representation_path_by_names( project_name: str, folder_path: str, product_name: str, - version_name: str, + version_name: Union[int, str], representation_name: str, anatomy: Optional[Anatomy] = None ) -> Optional[TemplateResult]: From b665bf3f79be43ba548d46fac8b970270c7085a8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:10:39 +0200 Subject: [PATCH 11/16] add attribute to the function to be able to detect if new version should be used --- client/ayon_core/pipeline/load/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 225d4e0d44..e1c9c31db6 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -725,6 +725,11 @@ def get_representation_path_with_roots( def _get_representation_path_decorator(func): + # Add an attribute to the function so addons can check if the new variant + # of the function is available. + # >>> getattr(get_representation_path, "version", None) == 2 + # >>> True + setattr(func, "version", 2) @wraps(func) def inner(*args, **kwargs): from ayon_core.pipeline import get_current_project_name From 4b2d2d5002c5ce3b6d1b8e126d43433778147353 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 Oct 2025 16:33:49 +0200 Subject: [PATCH 12/16] added overload definitions --- client/ayon_core/pipeline/load/utils.py | 55 ++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index e1c9c31db6..13c560bbd4 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -9,7 +9,7 @@ import collections import numbers import copy from functools import wraps -from typing import Optional, Union, Any +from typing import Optional, Union, Any, overload import ayon_api @@ -730,6 +730,7 @@ def _get_representation_path_decorator(func): # >>> getattr(get_representation_path, "version", None) == 2 # >>> True setattr(func, "version", 2) + @wraps(func) def inner(*args, **kwargs): from ayon_core.pipeline import get_current_project_name @@ -781,6 +782,58 @@ def _get_representation_path_decorator(func): return inner +@overload +def get_representation_path( + representation: dict[str, Any], + root: Optional[dict[str, Any]] = None, +) -> TemplateResult: + """DEPRECATED Get filled representation path. + + Use 'get_representation_path' using the new function signature. + + Args: + representation (dict[str, Any]): Representation entity. + root (Optional[dict[str, Any]): Roots to fill the path. + + Returns: + TemplateResult: Resolved path to representation. + + Raises: + InvalidRepresentationContext: When representation data are probably + invalid or not available. + + """ + pass + + +@overload +def get_representation_path( + project_name: str, + repre_entity: dict[str, Any], + *, + anatomy: Optional[Anatomy] = None, + project_entity: Optional[dict[str, Any]] = None, +) -> TemplateResult: + """Get filled representation path. + + Args: + project_name (str): Project name. + repre_entity (dict[str, Any]): Representation entity. + anatomy (Optional[Anatomy]): Project anatomy. + project_entity (Optional[dict[str, Any]): Project entity. Is used to + initialize Anatomy and is not needed if 'anatomy' is passed in. + + Returns: + TemplateResult: Resolved path to representation. + + Raises: + InvalidRepresentationContext: When representation data are probably + invalid or not available. + + """ + pass + + @_get_representation_path_decorator def get_representation_path( project_name: str, From 680766418869b7a2f82f096c5c3af4ec85db4b3b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:04:09 +0200 Subject: [PATCH 13/16] rename decorator function --- client/ayon_core/pipeline/load/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 13c560bbd4..70e2936e6f 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -724,7 +724,7 @@ def get_representation_path_with_roots( return path.normalized() -def _get_representation_path_decorator(func): +def _backwards_compatibility_repre_path(func): # Add an attribute to the function so addons can check if the new variant # of the function is available. # >>> getattr(get_representation_path, "version", None) == 2 @@ -834,7 +834,7 @@ def get_representation_path( pass -@_get_representation_path_decorator +@_backwards_compatibility_repre_path def get_representation_path( project_name: str, repre_entity: dict[str, Any], From 50531fa35af7a93a695eca086b3590018fded2e4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:23:39 +0200 Subject: [PATCH 14/16] added docstring --- client/ayon_core/pipeline/load/utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 70e2936e6f..7fb0c30331 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -725,6 +725,27 @@ def get_representation_path_with_roots( def _backwards_compatibility_repre_path(func): + """Wrapper handling backwards compatibility of 'get_representation_path'. + + Allows 'get_representation_path' to support old and new signatures of the + function. The old signature supported passing in representation entity + and optional roots. The new signature requires the project name + to be passed. In case custom roots should be used, a dedicated function + 'get_representation_path_with_roots' is available. + + The wrapper handles passed arguments, and based on kwargs and types + of the arguments will call the function which relates to + the arguments. + + 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_representation_path, "version", None) == 2: + >>> path = get_representation_path(project_name, repre_entity) + >>> else: + >>> path = get_representation_path(repre_entity) + + """ # Add an attribute to the function so addons can check if the new variant # of the function is available. # >>> getattr(get_representation_path, "version", None) == 2 From fbf370befa36a0ea9f471338d9d05cbea2e1710a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:23:53 +0200 Subject: [PATCH 15/16] raise from previous exception --- client/ayon_core/pipeline/load/utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 7fb0c30331..c4cf37d69d 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -886,11 +886,10 @@ def get_representation_path( try: template = repre_entity["attrib"]["template"] - except KeyError: + except KeyError as exc: raise InvalidRepresentationContext( - "Representation document does not" - " contain template in data ('data.template')" - ) + "Failed to receive template from representation entity." + ) from exc try: context = copy.deepcopy(repre_entity["context"]) @@ -901,9 +900,8 @@ def get_representation_path( except TemplateUnsolved as exc: raise InvalidRepresentationContext( - "Couldn't resolve representation template with available data." - f" Reason: {str(exc)}" - ) + "Failed to resolve representation template with available data." + ) from exc return path.normalized() From bd0320f56fd4ff2986f7afe4fa99f23a9c7702f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:24:04 +0200 Subject: [PATCH 16/16] added planned break of backwards compatibility --- client/ayon_core/pipeline/load/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index c4cf37d69d..8aed7b8b52 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -745,6 +745,8 @@ def _backwards_compatibility_repre_path(func): >>> else: >>> path = get_representation_path(repre_entity) + The plan to remove backwards compatibility is 1.1.2026. + """ # Add an attribute to the function so addons can check if the new variant # of the function is available. @@ -770,7 +772,7 @@ def _backwards_compatibility_repre_path(func): ( "Used deprecated variant of 'get_representation_path'." " Please change used arguments signature to follow" - " new definition." + " new definition. Will be removed 1.1.2026." ), DeprecationWarning, stacklevel=2,