Skip to content

load

IncompatibleLoaderError

Bases: ValueError

Error when Loader is incompatible with a representation.

Source code in client/ayon_core/pipeline/load/utils.py
58
59
60
class IncompatibleLoaderError(ValueError):
    """Error when Loader is incompatible with a representation."""
    pass

InvalidRepresentationContext

Bases: ValueError

Representation path can't be received using representation document.

Source code in client/ayon_core/pipeline/load/utils.py
63
64
65
class InvalidRepresentationContext(ValueError):
    """Representation path can't be received using representation document."""
    pass

LoadError

Bases: Exception

Known error that happened during loading.

A message is shown to user (without traceback). Make sure an artist can understand the problem.

Source code in client/ayon_core/pipeline/load/utils.py
48
49
50
51
52
53
54
55
class LoadError(Exception):
    """Known error that happened during loading.

    A message is shown to user (without traceback). Make sure an artist can
    understand the problem.
    """

    pass

LoaderNotFoundError

Bases: RuntimeError

Error when Loader plugin for a loader name is not found.

Source code in client/ayon_core/pipeline/load/utils.py
73
74
75
class LoaderNotFoundError(RuntimeError):
    """Error when Loader plugin for a loader name is not found."""
    pass

LoaderPlugin

Bases: list

Load representation into host application

Source code in client/ayon_core/pipeline/load/plugins.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
class LoaderPlugin(list):
    """Load representation into host application"""

    product_types = set()
    representations = set()
    extensions = {"*"}
    order = 0
    is_multiple_contexts_compatible = False
    enabled = True

    options = []

    log = logging.getLogger("ProductLoader")
    log.propagate = True

    @classmethod
    def apply_settings(cls, project_settings):
        host_name = os.environ.get("AYON_HOST_NAME")
        plugin_type = "load"
        plugin_type_settings = (
            project_settings
            .get(host_name, {})
            .get(plugin_type, {})
        )
        global_type_settings = (
            project_settings
            .get("core", {})
            .get(plugin_type, {})
        )
        if not global_type_settings and not plugin_type_settings:
            return

        plugin_name = cls.__name__

        plugin_settings = None
        # Look for plugin settings in host specific settings
        if plugin_name in plugin_type_settings:
            plugin_settings = plugin_type_settings[plugin_name]

        # Look for plugin settings in global settings
        elif plugin_name in global_type_settings:
            plugin_settings = global_type_settings[plugin_name]

        if not plugin_settings:
            return

        print(">>> We have preset for {}".format(plugin_name))
        for option, value in plugin_settings.items():
            if option == "enabled" and value is False:
                print("  - is disabled by preset")
            else:
                print("  - setting `{}`: `{}`".format(option, value))
            setattr(cls, option, value)

    @classmethod
    def has_valid_extension(cls, repre_entity):
        """Has representation document valid extension for loader.

        Args:
            repre_entity (dict[str, Any]): Representation entity.

        Returns:
             bool: Representation has valid extension
        """

        if "*" in cls.extensions:
            return True

        # Get representation main file extension from 'context'
        repre_context = repre_entity.get("context") or {}
        ext = repre_context.get("ext")
        if not ext:
            # Legacy way how to get extensions
            path = repre_entity.get("attrib", {}).get("path")
            if not path:
                cls.log.info(
                    "Representation doesn't have known source of extension"
                    " information."
                )
                return False

            cls.log.debug("Using legacy source of extension from path.")
            ext = os.path.splitext(path)[-1].lstrip(".")

        # If representation does not have extension then can't be valid
        if not ext:
            return False

        valid_extensions_low = {ext.lower() for ext in cls.extensions}
        return ext.lower() in valid_extensions_low

    @classmethod
    def is_compatible_loader(cls, context):
        """Return whether a loader is compatible with a context.

        On override make sure it is overridden as class or static method.

        This checks the product type and the representation for the given
        loader plugin.

        Args:
            context (dict[str, Any]): Documents of context for which should
                be loader used.

        Returns:
            bool: Is loader compatible for context.
        """

        plugin_repre_names = cls.get_representations()
        plugin_product_types = cls.product_types
        if (
            not plugin_repre_names
            or not plugin_product_types
            or not cls.extensions
        ):
            return False

        repre_entity = context.get("representation")
        if not repre_entity:
            return False

        plugin_repre_names = set(plugin_repre_names)
        if (
            "*" not in plugin_repre_names
            and repre_entity["name"] not in plugin_repre_names
        ):
            return False

        if not cls.has_valid_extension(repre_entity):
            return False

        plugin_product_types = set(plugin_product_types)
        if "*" in plugin_product_types:
            return True

        product_entity = context["product"]
        product_type = product_entity["productType"]

        return product_type in plugin_product_types

    @classmethod
    def get_representations(cls):
        """Representation names with which is plugin compatible.

        Empty set makes the plugin incompatible with any representation. To
            allow compatibility with all representations use '{"*"}'.

        Returns:
            set[str]: Names with which is plugin compatible.

        """
        return cls.representations

    @classmethod
    def filepath_from_context(cls, context):
        return get_representation_path_from_context(context)

    def load(self, context, name=None, namespace=None, options=None):
        """Load asset via database

        Arguments:
            context (dict): Full parenthood of representation to load
            name (str, optional): Use pre-defined name
            namespace (str, optional): Use pre-defined namespace
            options (dict, optional): Additional settings dictionary

        """
        raise NotImplementedError("Loader.load() must be "
                                  "implemented by subclass")

    def update(self, container, context):
        """Update `container` to `representation`

        Args:
            container (avalon-core:container-1.0): Container to update,
                from `host.ls()`.
            context (dict): Update the container to this representation.

        """
        raise NotImplementedError("Loader.update() must be "
                                  "implemented by subclass")

    def remove(self, container):
        """Remove a container

        Arguments:
            container (avalon-core:container-1.0): Container to remove,
                from `host.ls()`.

        Returns:
            bool: Whether the container was deleted

        """

        raise NotImplementedError("Loader.remove() must be "
                                  "implemented by subclass")

    @classmethod
    def get_options(cls, contexts):
        """
            Returns static (cls) options or could collect from 'contexts'.

            Args:
                contexts (list): of repre or product contexts
            Returns:
                (list)
        """
        return cls.options or []

    @property
    def fname(self):
        """Backwards compatibility with deprecation warning"""

        self.log.warning((
            "DEPRECATION WARNING: Source - Loader plugin {}."
            " The 'fname' property on the Loader plugin will be removed in"
            " future versions of OpenPype. Planned version to drop the support"
            " is 3.16.6 or 3.17.0."
        ).format(self.__class__.__name__))
        if hasattr(self, "_fname"):
            return self._fname

    @classmethod
    def get_representation_name_aliases(cls, representation_name: str):
        """Return representation names to which switching is allowed from
        the input representation name, like an alias replacement of the input
        `representation_name`.

        For example, to allow an automated switch on update from representation
        `ma` to `mb` or `abc`, then when `representation_name` is `ma` return:
            ["mb", "abc"]

        The order of the names in the returned representation names is
        important, because the first one existing under the new version will
        be chosen.

        Returns:
            List[str]: Representation names switching to is allowed on update
              if the input representation name is not found on the new version.
        """
        return []

fname property

Backwards compatibility with deprecation warning

get_options(contexts) classmethod

Returns static (cls) options or could collect from 'contexts'.

Parameters:

Name Type Description Default
contexts list

of repre or product contexts

required

Returns: (list)

Source code in client/ayon_core/pipeline/load/plugins.py
212
213
214
215
216
217
218
219
220
221
222
@classmethod
def get_options(cls, contexts):
    """
        Returns static (cls) options or could collect from 'contexts'.

        Args:
            contexts (list): of repre or product contexts
        Returns:
            (list)
    """
    return cls.options or []

get_representation_name_aliases(representation_name) classmethod

Return representation names to which switching is allowed from the input representation name, like an alias replacement of the input representation_name.

For example, to allow an automated switch on update from representation ma to mb or abc, then when representation_name is ma return: ["mb", "abc"]

The order of the names in the returned representation names is important, because the first one existing under the new version will be chosen.

Returns:

Type Description

List[str]: Representation names switching to is allowed on update if the input representation name is not found on the new version.

Source code in client/ayon_core/pipeline/load/plugins.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
@classmethod
def get_representation_name_aliases(cls, representation_name: str):
    """Return representation names to which switching is allowed from
    the input representation name, like an alias replacement of the input
    `representation_name`.

    For example, to allow an automated switch on update from representation
    `ma` to `mb` or `abc`, then when `representation_name` is `ma` return:
        ["mb", "abc"]

    The order of the names in the returned representation names is
    important, because the first one existing under the new version will
    be chosen.

    Returns:
        List[str]: Representation names switching to is allowed on update
          if the input representation name is not found on the new version.
    """
    return []

get_representations() classmethod

Representation names with which is plugin compatible.

Empty set makes the plugin incompatible with any representation. To allow compatibility with all representations use '{"*"}'.

Returns:

Type Description

set[str]: Names with which is plugin compatible.

Source code in client/ayon_core/pipeline/load/plugins.py
155
156
157
158
159
160
161
162
163
164
165
166
@classmethod
def get_representations(cls):
    """Representation names with which is plugin compatible.

    Empty set makes the plugin incompatible with any representation. To
        allow compatibility with all representations use '{"*"}'.

    Returns:
        set[str]: Names with which is plugin compatible.

    """
    return cls.representations

has_valid_extension(repre_entity) classmethod

Has representation document valid extension for loader.

Parameters:

Name Type Description Default
repre_entity dict[str, Any]

Representation entity.

required

Returns:

Name Type Description
bool

Representation has valid extension

Source code in client/ayon_core/pipeline/load/plugins.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
@classmethod
def has_valid_extension(cls, repre_entity):
    """Has representation document valid extension for loader.

    Args:
        repre_entity (dict[str, Any]): Representation entity.

    Returns:
         bool: Representation has valid extension
    """

    if "*" in cls.extensions:
        return True

    # Get representation main file extension from 'context'
    repre_context = repre_entity.get("context") or {}
    ext = repre_context.get("ext")
    if not ext:
        # Legacy way how to get extensions
        path = repre_entity.get("attrib", {}).get("path")
        if not path:
            cls.log.info(
                "Representation doesn't have known source of extension"
                " information."
            )
            return False

        cls.log.debug("Using legacy source of extension from path.")
        ext = os.path.splitext(path)[-1].lstrip(".")

    # If representation does not have extension then can't be valid
    if not ext:
        return False

    valid_extensions_low = {ext.lower() for ext in cls.extensions}
    return ext.lower() in valid_extensions_low

is_compatible_loader(context) classmethod

Return whether a loader is compatible with a context.

On override make sure it is overridden as class or static method.

This checks the product type and the representation for the given loader plugin.

Parameters:

Name Type Description Default
context dict[str, Any]

Documents of context for which should be loader used.

required

Returns:

Name Type Description
bool

Is loader compatible for context.

Source code in client/ayon_core/pipeline/load/plugins.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
@classmethod
def is_compatible_loader(cls, context):
    """Return whether a loader is compatible with a context.

    On override make sure it is overridden as class or static method.

    This checks the product type and the representation for the given
    loader plugin.

    Args:
        context (dict[str, Any]): Documents of context for which should
            be loader used.

    Returns:
        bool: Is loader compatible for context.
    """

    plugin_repre_names = cls.get_representations()
    plugin_product_types = cls.product_types
    if (
        not plugin_repre_names
        or not plugin_product_types
        or not cls.extensions
    ):
        return False

    repre_entity = context.get("representation")
    if not repre_entity:
        return False

    plugin_repre_names = set(plugin_repre_names)
    if (
        "*" not in plugin_repre_names
        and repre_entity["name"] not in plugin_repre_names
    ):
        return False

    if not cls.has_valid_extension(repre_entity):
        return False

    plugin_product_types = set(plugin_product_types)
    if "*" in plugin_product_types:
        return True

    product_entity = context["product"]
    product_type = product_entity["productType"]

    return product_type in plugin_product_types

load(context, name=None, namespace=None, options=None)

Load asset via database

Parameters:

Name Type Description Default
context dict

Full parenthood of representation to load

required
name str

Use pre-defined name

None
namespace str

Use pre-defined namespace

None
options dict

Additional settings dictionary

None
Source code in client/ayon_core/pipeline/load/plugins.py
172
173
174
175
176
177
178
179
180
181
182
183
def load(self, context, name=None, namespace=None, options=None):
    """Load asset via database

    Arguments:
        context (dict): Full parenthood of representation to load
        name (str, optional): Use pre-defined name
        namespace (str, optional): Use pre-defined namespace
        options (dict, optional): Additional settings dictionary

    """
    raise NotImplementedError("Loader.load() must be "
                              "implemented by subclass")

remove(container)

Remove a container

Parameters:

Name Type Description Default
container avalon - core

container-1.0): Container to remove, from host.ls().

required

Returns:

Name Type Description
bool

Whether the container was deleted

Source code in client/ayon_core/pipeline/load/plugins.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def remove(self, container):
    """Remove a container

    Arguments:
        container (avalon-core:container-1.0): Container to remove,
            from `host.ls()`.

    Returns:
        bool: Whether the container was deleted

    """

    raise NotImplementedError("Loader.remove() must be "
                              "implemented by subclass")

update(container, context)

Update container to representation

Parameters:

Name Type Description Default
container avalon - core

container-1.0): Container to update, from host.ls().

required
context dict

Update the container to this representation.

required
Source code in client/ayon_core/pipeline/load/plugins.py
185
186
187
188
189
190
191
192
193
194
195
def update(self, container, context):
    """Update `container` to `representation`

    Args:
        container (avalon-core:container-1.0): Container to update,
            from `host.ls()`.
        context (dict): Update the container to this representation.

    """
    raise NotImplementedError("Loader.update() must be "
                              "implemented by subclass")

LoaderSwitchNotImplementedError

Bases: NotImplementedError

Error when switch is used with Loader that has no implementation.

Source code in client/ayon_core/pipeline/load/utils.py
68
69
70
class LoaderSwitchNotImplementedError(NotImplementedError):
    """Error when `switch` is used with Loader that has no implementation."""
    pass

ProductLoaderPlugin

Bases: LoaderPlugin

Load product into host application Arguments: context (dict): avalon-core:context-1.0 name (str, optional): Use pre-defined name namespace (str, optional): Use pre-defined namespace

Source code in client/ayon_core/pipeline/load/plugins.py
258
259
260
261
262
263
264
class ProductLoaderPlugin(LoaderPlugin):
    """Load product into host application
    Arguments:
        context (dict): avalon-core:context-1.0
        name (str, optional): Use pre-defined name
        namespace (str, optional): Use pre-defined namespace
    """

any_outdated_containers(host=None, project_name=None)

Check if there are any outdated containers in scene.

Source code in client/ayon_core/pipeline/load/utils.py
921
922
923
924
925
926
def any_outdated_containers(host=None, project_name=None):
    """Check if there are any outdated containers in scene."""

    if get_outdated_containers(host, project_name):
        return True
    return False

filter_containers(containers, project_name)

Filter containers and split them into 4 categories.

Categories are 'latest', 'outdated', 'invalid' and 'not_found'. The 'lastest' containers are from last version, 'outdated' are not, 'invalid' are invalid containers (invalid content) and 'not_found' has some missing entity in database.

Parameters:

Name Type Description Default
containers Iterable[dict]

List of containers referenced into scene.

required
project_name str

Name of project in which context shoud look for versions.

required

Returns:

Name Type Description
ContainersFilterResult

Named tuple with 'latest', 'outdated', 'invalid' and 'not_found' containers.

Source code in client/ayon_core/pipeline/load/utils.py
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
def filter_containers(containers, project_name):
    """Filter containers and split them into 4 categories.

    Categories are 'latest', 'outdated', 'invalid' and 'not_found'.
    The 'lastest' containers are from last version, 'outdated' are not,
    'invalid' are invalid containers (invalid content) and 'not_found' has
    some missing entity in database.

    Args:
        containers (Iterable[dict]): List of containers referenced into scene.
        project_name (str): Name of project in which context shoud look for
            versions.

    Returns:
        ContainersFilterResult: Named tuple with 'latest', 'outdated',
            'invalid' and 'not_found' containers.
    """

    # Make sure containers is list that won't change
    containers = list(containers)

    outdated_containers = []
    uptodate_containers = []
    not_found_containers = []
    invalid_containers = []
    output = ContainersFilterResult(
        uptodate_containers,
        outdated_containers,
        not_found_containers,
        invalid_containers
    )
    # Query representation docs to get it's version ids
    repre_ids = {
        container["representation"]
        for container in containers
        if _is_valid_representation_id(container["representation"])
    }
    if not repre_ids:
        if containers:
            invalid_containers.extend(containers)
        return output

    repre_entities = ayon_api.get_representations(
        project_name,
        representation_ids=repre_ids,
        fields={"id", "versionId"}
    )
    # Store representations by stringified representation id
    repre_entities_by_id = {}
    repre_entities_by_version_id = collections.defaultdict(list)
    for repre_entity in repre_entities:
        repre_id = repre_entity["id"]
        version_id = repre_entity["versionId"]
        repre_entities_by_id[repre_id] = repre_entity
        repre_entities_by_version_id[version_id].append(repre_entity)

    # Query version docs to get it's product ids
    # - also query hero version to be able identify if representation
    #   belongs to existing version
    version_entities = ayon_api.get_versions(
        project_name,
        version_ids=repre_entities_by_version_id.keys(),
        hero=True,
        fields={"id", "productId", "version"}
    )
    verisons_by_id = {}
    versions_by_product_id = collections.defaultdict(list)
    hero_version_ids = set()
    for version_entity in version_entities:
        version_id = version_entity["id"]
        # Store versions by their ids
        verisons_by_id[version_id] = version_entity
        # There's no need to query products for hero versions
        #   - they are considered as latest?
        if version_entity["version"] < 0:
            hero_version_ids.add(version_id)
            continue
        product_id = version_entity["productId"]
        versions_by_product_id[product_id].append(version_entity)

    last_versions = ayon_api.get_last_versions(
        project_name,
        versions_by_product_id.keys(),
        fields={"id"}
    )
    # Figure out which versions are outdated
    outdated_version_ids = set()
    for product_id, last_version_entity in last_versions.items():
        for version_entity in versions_by_product_id[product_id]:
            version_id = version_entity["id"]
            if version_id in hero_version_ids:
                continue
            if version_id != last_version_entity["id"]:
                outdated_version_ids.add(version_id)

    # Based on all collected data figure out which containers are outdated
    #   - log out if there are missing representation or version documents
    for container in containers:
        container_name = container["objectName"]
        repre_id = container["representation"]
        if not _is_valid_representation_id(repre_id):
            invalid_containers.append(container)
            continue

        repre_entity = repre_entities_by_id.get(repre_id)
        if not repre_entity:
            log.debug((
                "Container '{}' has an invalid representation."
                " It is missing in the database."
            ).format(container_name))
            not_found_containers.append(container)
            continue

        version_id = repre_entity["versionId"]
        if version_id in outdated_version_ids:
            outdated_containers.append(container)

        elif version_id not in verisons_by_id:
            log.debug((
                "Representation on container '{}' has an invalid version."
                " It is missing in the database."
            ).format(container_name))
            not_found_containers.append(container)

        else:
            uptodate_containers.append(container)

    return output

filter_repre_contexts_by_loader(repre_contexts, loader)

Filter representation contexts for loader.

Parameters:

Name Type Description Default
repre_contexts list[dict[str, Ant]]

Representation context.

required
loader LoaderPlugin

Loader plugin to filter contexts for.

required

Returns:

Type Description

list[dict[str, Any]]: Filtered representation contexts.

Source code in client/ayon_core/pipeline/load/utils.py
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
def filter_repre_contexts_by_loader(repre_contexts, loader):
    """Filter representation contexts for loader.

    Args:
        repre_contexts (list[dict[str, Ant]]): Representation context.
        loader (LoaderPlugin): Loader plugin to filter contexts for.

    Returns:
        list[dict[str, Any]]: Filtered representation contexts.
    """

    return [
        repre_context
        for repre_context in repre_contexts
        if is_compatible_loader(loader, repre_context)
    ]

get_loader_identifier(loader)

Loader identifier from loader plugin or object.

Identifier should be stored to container for future management.

Source code in client/ayon_core/pipeline/load/utils.py
414
415
416
417
418
419
420
421
def get_loader_identifier(loader):
    """Loader identifier from loader plugin or object.

    Identifier should be stored to container for future management.
    """
    if not inspect.isclass(loader):
        loader = loader.__class__
    return loader.__name__

get_outdated_containers(host=None, project_name=None)

Collect outdated containers from host scene.

Currently registered host and project in global session are used if arguments are not passed.

Parameters:

Name Type Description Default
host ModuleType

Host implementation with 'ls' function available.

None
project_name str

Name of project in which context we are.

None
Source code in client/ayon_core/pipeline/load/utils.py
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
def get_outdated_containers(host=None, project_name=None):
    """Collect outdated containers from host scene.

    Currently registered host and project in global session are used if
    arguments are not passed.

    Args:
        host (ModuleType): Host implementation with 'ls' function available.
        project_name (str): Name of project in which context we are.
    """
    from ayon_core.pipeline import registered_host, get_current_project_name

    if host is None:
        host = registered_host()

    if project_name is None:
        project_name = get_current_project_name()

    if isinstance(host, ILoadHost):
        containers = host.get_containers()
    else:
        containers = host.ls()
    return filter_containers(containers, project_name).outdated

get_product_contexts(product_ids, project_name=None)

Return parenthood context for product.

Provides context on product granularity - less detail than
'get_repre_contexts'.

Args: product_ids (list): The product ids. project_name (Optional[str]): Project name. Returns: dict: The full representation context by representation id.

Source code in client/ayon_core/pipeline/load/utils.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def get_product_contexts(product_ids, project_name=None):
    """Return parenthood context for product.

        Provides context on product granularity - less detail than
        'get_repre_contexts'.
    Args:
        product_ids (list): The product ids.
        project_name (Optional[str]): Project name.
    Returns:
        dict: The full representation context by representation id.
    """
    from ayon_core.pipeline import get_current_project_name

    contexts = {}
    if not product_ids:
        return contexts

    if not project_name:
        project_name = get_current_project_name()
    product_entities = ayon_api.get_products(
        project_name, product_ids=product_ids
    )
    product_entities_by_id = {}
    folder_ids = set()
    for product_entity in product_entities:
        product_entities_by_id[product_entity["id"]] = product_entity
        folder_ids.add(product_entity["folderId"])

    folder_entities_by_id = {
        folder_entity["id"]: folder_entity
        for folder_entity in ayon_api.get_folders(
            project_name, folder_ids=folder_ids
        )
    }

    project_entity = ayon_api.get_project(project_name)

    for product_id, product_entity in product_entities_by_id.items():
        folder_entity = folder_entities_by_id[product_entity["folderId"]]
        context = {
            "project": project_entity,
            "folder": folder_entity,
            "product": product_entity
        }
        contexts[product_id] = context

    return contexts

get_repres_contexts(representation_ids, project_name=None)

Return parenthood context for representation.

Parameters:

Name Type Description Default
representation_ids list

The representation ids.

required
project_name Optional[str]

Project name.

None

Returns:

Name Type Description
dict

The full representation context by representation id. keys are repre_id, value is dictionary with entities of folder, product, version and representation.

Source code in client/ayon_core/pipeline/load/utils.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def get_repres_contexts(representation_ids, project_name=None):
    """Return parenthood context for representation.

    Args:
        representation_ids (list): The representation ids.
        project_name (Optional[str]): Project name.

    Returns:
        dict: The full representation context by representation id.
            keys are repre_id, value is dictionary with entities of
            folder, product, version and representation.
    """
    from ayon_core.pipeline import get_current_project_name

    if not representation_ids:
        return {}

    if not project_name:
        project_name = get_current_project_name()

    repre_entities = ayon_api.get_representations(
        project_name, representation_ids
    )

    return get_representation_contexts(project_name, repre_entities)

get_representation_context(project_name, representation)

Return parenthood context for representation.

Parameters:

Name Type Description Default
project_name str

Project name.

required
representation Union[dict[str, Any], str]

Representation entity or representation id.

required

Returns:

Type Description

dict[str, dict[str, Any]]: The full representation context.

Raises:

Type Description
ValueError

When representation is invalid or parents were not found.

Source code in client/ayon_core/pipeline/load/utils.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
def get_representation_context(project_name, representation):
    """Return parenthood context for representation.

    Args:
        project_name (str): Project name.
        representation (Union[dict[str, Any], str]): Representation entity
            or representation id.

    Returns:
        dict[str, dict[str, Any]]: The full representation context.

    Raises:
        ValueError: When representation is invalid or parents were not found.

    """
    if not representation:
        raise ValueError(
            "Invalid argument value {}".format(str(representation))
        )

    if isinstance(representation, dict):
        repre_entity = representation
        repre_id = repre_entity["id"]
        context = get_representation_contexts(
            project_name, [repre_entity]
        )[repre_id]
    else:
        repre_id = representation
        context = get_representation_contexts_by_ids(
            project_name, {repre_id}
        )[repre_id]

    missing_entities = []
    for key, value in context.items():
        if value is None:
            missing_entities.append(key)

    if missing_entities:
        raise ValueError(
            "Not able to receive representation parent types: {}".format(
                ", ".join(missing_entities)
            )
        )

    return context

get_representation_contexts(project_name, representation_entities)

Parenthood context for representations.

Function fills None if any entity was not found or could not be queried.

Parameters:

Name Type Description Default
project_name str

Project name.

required
representation_entities Iterable[dict[str, Any]]

Representation entities.

required

Returns:

Type Description

dict[str, dict[str, Any]]: The full representation context by representation id.

Source code in client/ayon_core/pipeline/load/utils.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def get_representation_contexts(project_name, representation_entities):
    """Parenthood context for representations.

    Function fills ``None`` if any entity was not found or could
        not be queried.

    Args:
        project_name (str): Project name.
        representation_entities (Iterable[dict[str, Any]]): Representation
            entities.

    Returns:
        dict[str, dict[str, Any]]: The full representation context by
            representation id.

    """
    repre_entities_by_id = {
        repre_entity["id"]: repre_entity
        for repre_entity in representation_entities
    }

    if not repre_entities_by_id:
        return {}

    repre_ids = set(repre_entities_by_id)

    parents_by_repre_id = ayon_api.get_representations_parents(
        project_name, repre_ids
    )
    output = {}
    for repre_id in repre_ids:
        repre_entity = repre_entities_by_id[repre_id]
        (
            version_entity,
            product_entity,
            folder_entity,
            project_entity
        ) = parents_by_repre_id[repre_id]
        output[repre_id] = {
            "project": project_entity,
            "folder": folder_entity,
            "product": product_entity,
            "version": version_entity,
            "representation": repre_entity,
        }
    return output

get_representation_contexts_by_ids(project_name, representation_ids)

Parenthood context for representations found by ids.

Function fills None if any entity was not found or could not be queried.

Parameters:

Name Type Description Default
project_name str

Project name.

required
representation_ids Iterable[str]

Representation ids.

required

Returns:

Type Description

dict[str, dict[str, Any]]: The full representation context by representation id.

Source code in client/ayon_core/pipeline/load/utils.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def get_representation_contexts_by_ids(project_name, representation_ids):
    """Parenthood context for representations found by ids.

    Function fills ``None`` if any entity was not found or could
        not be queried.

    Args:
        project_name (str): Project name.
        representation_ids (Iterable[str]): Representation ids.

    Returns:
        dict[str, dict[str, Any]]: The full representation context by
            representation id.

    """
    repre_ids = set(representation_ids)
    if not repre_ids:
        return {}

    # Query representation entities by id
    repre_entities_by_id = {
        repre_entity["id"]: repre_entity
        for repre_entity in ayon_api.get_representations(
            project_name, repre_ids
        )
    }
    output = get_representation_contexts(
        project_name, repre_entities_by_id.values()
    )
    for repre_id in repre_ids:
        if repre_id not in output:
            output[repre_id] = {
                "project": None,
                "folder": None,
                "product": None,
                "version": None,
                "representation": None,
            }
    return output

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

Parameters:

Name Type Description Default
representation(dict)

representation document from the database

required

Returns:

Name Type Description
str

fullpath of the representation

Source code in client/ayon_core/pipeline/load/utils.py
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
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

    Args:
        representation(dict): representation document from the database

    Returns:
        str: fullpath of the representation

    """

    if root is None:
        from ayon_core.pipeline import registered_root

        root = registered_root()

    def path_from_representation():
        try:
            template = representation["attrib"]["template"]
        except KeyError:
            return None

        try:
            context = representation["context"]

            _fix_representation_context_compatibility(context)

            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

        if not path:
            return path

        normalized_path = os.path.normpath(path)
        if os.path.exists(normalized_path):
            return normalized_path
        return path

    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("\\", "/")

        if os.path.exists(path):
            return os.path.normpath(path)

        dir_path, file_name = os.path.split(path)
        if not os.path.exists(dir_path):
            return

        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

        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()
    )

get_representation_path_from_context(context)

Preparation wrapper using only context as a argument

Source code in client/ayon_core/pipeline/load/utils.py
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def get_representation_path_from_context(context):
    """Preparation wrapper using only context as a argument"""
    from ayon_core.pipeline import get_current_project_name

    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)

get_representation_path_with_anatomy(repre_entity, anatomy)

Receive representation path using representation document and anatomy.

Anatomy is used to replace 'root' key in representation file. Ideally should be used instead of 'get_representation_path' which is based on "current context".

Future notes

We want also be able store resources into representation and I can imagine the result should also contain paths to possible resources.

Parameters:

Name Type Description Default
repre_entity Dict[str, Any]

Representation entity.

required
anatomy Anatomy

Project anatomy object.

required

Returns:

Type Description

Union[None, TemplateResult]: None if path can't be received

Raises:

Type Description
InvalidRepresentationContext

When representation data are probably invalid or not available.

Source code in client/ayon_core/pipeline/load/utils.py
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
def get_representation_path_with_anatomy(repre_entity, anatomy):
    """Receive representation path using representation document and anatomy.

    Anatomy is used to replace 'root' key in representation file. Ideally
    should be used instead of 'get_representation_path' which is based on
    "current context".

    Future notes:
        We want also be able store resources into representation and I can
        imagine the result should also contain paths to possible resources.

    Args:
        repre_entity (Dict[str, Any]): Representation entity.
        anatomy (Anatomy): Project anatomy object.

    Returns:
        Union[None, TemplateResult]: None if path can't be received

    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()

is_compatible_loader(Loader, context)

Return whether a loader is compatible with a context.

This checks the product type and the representation for the given Loader.

Returns:

Type Description

bool

Source code in client/ayon_core/pipeline/load/utils.py
869
870
871
872
873
874
875
876
877
878
879
def is_compatible_loader(Loader, context):
    """Return whether a loader is compatible with a context.

    This checks the product type and the representation for the given
    Loader.

    Returns:
        bool
    """

    return Loader.is_compatible_loader(context)

load_container(Loader, representation, namespace=None, name=None, options=None, **kwargs)

Use Loader to load a representation.

Parameters:

Name Type Description Default
Loader Loader

The loader class to trigger.

required
representation str or dict

The representation id or full representation as returned by the database.

required
namespace (str, Optional)

The namespace to assign. Defaults to None.

None
name (str, Optional)

The name to assign. Defaults to product name.

None
options (dict, Optional)

Additional options to pass on to the loader.

None

Returns:

Type Description

The return of the loader.load() method.

Raises:

Type Description
IncompatibleLoaderError

When the loader is not compatible with the representation.

Source code in client/ayon_core/pipeline/load/utils.py
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
def load_container(
    Loader, representation, namespace=None, name=None, options=None, **kwargs
):
    """Use Loader to load a representation.

    Args:
        Loader (Loader): The loader class to trigger.
        representation (str or dict): The representation id
            or full representation as returned by the database.
        namespace (str, Optional): The namespace to assign. Defaults to None.
        name (str, Optional): The name to assign. Defaults to product name.
        options (dict, Optional): Additional options to pass on to the loader.

    Returns:
        The return of the `loader.load()` method.

    Raises:
        IncompatibleLoaderError: When the loader is not compatible with
            the representation.

    """
    from ayon_core.pipeline import get_current_project_name

    context = get_representation_context(
        get_current_project_name(), representation
    )
    return load_with_repre_context(
        Loader,
        context,
        namespace=namespace,
        name=name,
        options=options,
        **kwargs
    )

loaders_from_repre_context(loaders, repre_context)

Return compatible loaders for by representaiton's context.

Source code in client/ayon_core/pipeline/load/utils.py
882
883
884
885
886
887
888
889
def loaders_from_repre_context(loaders, repre_context):
    """Return compatible loaders for by representaiton's context."""

    return [
        loader
        for loader in loaders
        if is_compatible_loader(loader, repre_context)
    ]

loaders_from_representation(loaders, representation)

Return all compatible loaders for a representation.

Source code in client/ayon_core/pipeline/load/utils.py
910
911
912
913
914
915
916
917
918
def loaders_from_representation(loaders, representation):
    """Return all compatible loaders for a representation."""
    from ayon_core.pipeline import get_current_project_name

    project_name = get_current_project_name()
    context = get_representation_context(
        project_name, representation
    )
    return loaders_from_repre_context(loaders, context)

remove_container(container)

Remove a container

Source code in client/ayon_core/pipeline/load/utils.py
450
451
452
453
454
455
456
457
458
459
460
def remove_container(container):
    """Remove a container"""

    Loader = _get_container_loader(container)
    if not Loader:
        raise LoaderNotFoundError(
            "Can't remove container because loader '{}' was not found."
            .format(container.get("loader"))
        )

    return Loader().remove(container)

switch_container(container, representation, loader_plugin=None)

Switch a container to representation

Parameters:

Name Type Description Default
container dict

container information

required
representation dict

representation entity

required

Returns:

Type Description

function call

Source code in client/ayon_core/pipeline/load/utils.py
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
def switch_container(container, representation, loader_plugin=None):
    """Switch a container to representation

    Args:
        container (dict): container information
        representation (dict): representation entity

    Returns:
        function call
    """
    from ayon_core.pipeline import get_current_project_name

    # Get the Loader for this container
    if loader_plugin is None:
        loader_plugin = _get_container_loader(container)

    if not loader_plugin:
        raise LoaderNotFoundError(
            "Can't switch container because loader '{}' was not found."
            .format(container.get("loader"))
        )

    if not hasattr(loader_plugin, "switch"):
        # Backwards compatibility (classes without switch support
        # might be better to just have "switch" raise NotImplementedError
        # on the base class of Loader\
        raise LoaderSwitchNotImplementedError(
            "Loader {} does not support 'switch'".format(loader_plugin.label)
        )

    # Get the new representation to switch to
    project_name = container.get("project_name")
    if project_name is None:
        project_name = get_current_project_name()

    context = get_representation_context(
        project_name, representation["id"]
    )
    if not is_compatible_loader(loader_plugin, context):
        raise IncompatibleLoaderError(
            "Loader {} is incompatible with {}".format(
                loader_plugin.__name__, context["product"]["name"]
            )
        )

    loader = loader_plugin(context)

    return loader.switch(container, context)

update_container(container, version=-1)

Update a container

Source code in client/ayon_core/pipeline/load/utils.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
def update_container(container, version=-1):
    """Update a container"""
    from ayon_core.pipeline import get_current_project_name

    # Compute the different version from 'representation'
    project_name = container.get("project_name")
    if project_name is None:
        project_name = get_current_project_name()
    repre_id = container["representation"]
    if not _is_valid_representation_id(repre_id):
        raise ValueError(
            f"Got container with invalid representation id '{repre_id}'"
        )
    current_representation = ayon_api.get_representation_by_id(
        project_name, repre_id
    )

    assert current_representation is not None, "This is a bug"

    current_version_id = current_representation["versionId"]
    current_version = ayon_api.get_version_by_id(
        project_name, current_version_id, fields={"productId"}
    )
    if isinstance(version, HeroVersionType):
        new_version = ayon_api.get_hero_version_by_product_id(
            project_name, current_version["productId"]
        )
    elif version == -1:
        new_version = ayon_api.get_last_version_by_product_id(
            project_name, current_version["productId"]
        )

    else:
        new_version = ayon_api.get_version_by_name(
            project_name, version, current_version["productId"]
        )

    if new_version is None:
        raise ValueError("Failed to find matching version")

    product_entity = ayon_api.get_product_by_id(
        project_name, current_version["productId"]
    )
    folder_entity = ayon_api.get_folder_by_id(
        project_name, product_entity["folderId"]
    )

    # Run update on the Loader for this container
    Loader = _get_container_loader(container)
    if not Loader:
        raise LoaderNotFoundError(
            "Can't update container because loader '{}' was not found."
            .format(container.get("loader"))
        )

    repre_name = current_representation["name"]
    new_representation = ayon_api.get_representation_by_name(
        project_name, repre_name, new_version["id"]
    )
    if new_representation is None:
        # The representation name is not found in the new version.
        # Allow updating to a 'matching' representation if the loader
        # has defined compatible update conversions
        repre_name_aliases = Loader.get_representation_name_aliases(repre_name)
        if repre_name_aliases:
            representations = ayon_api.get_representations(
                project_name,
                representation_names=repre_name_aliases,
                version_ids=[new_version["id"]])
            representations_by_name = {
                repre["name"]: repre for repre in representations
            }
            for name in repre_name_aliases:
                if name in representations_by_name:
                    new_representation = representations_by_name[name]
                    break

        if new_representation is None:
            raise ValueError(
                "Representation '{}' wasn't found on requested version".format(
                    repre_name
                )
            )

    project_entity = ayon_api.get_project(project_name)
    context = {
        "project": project_entity,
        "folder": folder_entity,
        "product": product_entity,
        "version": new_version,
        "representation": new_representation,
    }
    path = get_representation_path_from_context(context)
    if not path or not os.path.exists(path):
        raise ValueError("Path {} doesn't exist".format(path))

    return Loader().update(container, context)