8000 🏛️Product base types: add support to loaders by antirotor · Pull Request #1301 · ynput/ayon-core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

🏛️Product base types: add support to loaders #1301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
91df75f
:sparkles: add product base type support to loaders
antirotor May 30, 2025
fac933c
:recycle: make the check backwards compatible
antirotor May 30, 2025
6ea717b
:wrench: WIP on product base type support in loader tool
antirotor Jun 6, 2025
3a2f470
:sparkles: show product base type in the loader
antirotor Jun 6, 2025
c8069dd
Merge branch 'develop' into enhancement/1294-product-base-types-suppo…
antirotor Jun 6, 2025
a3357a3
:dog: fix some linting issue
antirotor Jun 6, 2025
98eb281
:recycle: hide product base types if support is disabled
antirotor Jun 6, 2025
04fffd0
Merge remote-tracking branch 'origin/enhancement/1294-product-base-ty…
antirotor Jun 6, 2025
74ed8bf
:recycle: refactor support check function name
antirotor Jun 9, 2025
3cca210
Merge branch 'develop' into enhancement/1294-product-base-types-suppo…
antirotor Jun 9, 2025
26e2b45
Update client/ayon_core/tools/loader/abstract.py
antirotor Jun 13, 2025
1d66a86
Update client/ayon_core/tools/loader/abstract.py
antirotor Jun 13, 2025
1a39308
Merge branch 'develop' into enhancement/1294-product-base-types-suppo…
antirotor Jun 13, 2025
6443d46
Merge branch 'develop' into enhancement/1294-product-base-types-suppo…
antirotor Jun 18, 2025
f4af01f
:burn: remove `TypedDict` to retain compatibility with pythpn 3.7
antirotor Jun 18, 2025
ae1bfc7
:recycle: change loader filtering
antirotor Jun 18, 2025
f758420
Merge remote-tracking branch 'origin/enhancement/1294-product-base-ty…
antirotor Jun 18, 2025
1c63b75
:recycle: make product type and product base types None by default
antirotor Jun 19, 2025
a3c04d2
:recycle: revert more `TypedDict` changes and fix line length
antirotor Jun 19, 2025
e003ef2
:fire: revert some code cleanup
antirotor Jun 19, 2025
9738c2c
Update client/ayon_core/pipeline/load/plugins.py
antirotor Jun 19, 2025
929418c
Update client/ayon_core/pipeline/load/plugins.py
antirotor Jun 19, 2025
16ac7e2
Merge remote-tracking branch 'origin/develop' into enhancement/1294-p…
antirotor Jun 19, 2025
deacb28
:recycle: refactor filtering and add some tests
antirotor Jun 20, 2025
c1b9eff
:bug: fix comment and condition
antirotor Jun 23, 2025
5393981
Merge branch 'develop' into enhancement/1294-product-base-types-suppo…
antirotor Jun 23, 2025
2f9cd88
revert conditions
iLLiCiTiT Jun 24, 2025
4aefaca
remove unncessary product base type filters redefinitins
iLLiCiTiT Jun 24, 2025
3637294
Merge branch 'develop' into enhancement/1294-product-base-types-suppo…
iLLiCiTiT Jun 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 61 additions & 29 deletions client/ayon_core/pipeline/load/plugins.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
"""Plugins for loading representations and products into host applications."""
from __future__ import annotations
import os
import logging
from typing import Any, Type, Optional

from abc import abstractmethod
import logging
import os
from typing import Any, Optional, Type

from ayon_core.settings import get_project_settings
from ayon_core.pipeline.plugin_discover import (
deregister_plugin,
deregister_plugin_path,
discover,
register_plugin,
register_plugin_path,
deregister_plugin,
deregister_plugin_path
)
from ayon_core.settings import get_project_settings

from .utils import get_representation_path_from_context


class LoaderPlugin(list):
"""Load representation into host application"""

product_types = set()
product_types: set[str] = set()
product_base_types: Optional[set[str]] = None
representations = set()
extensions = {"*"}
order = 0
Expand Down Expand Up @@ -61,12 +65,12 @@ def apply_settings(cls, project_settings):
if not plugin_settings:
return

print(">>> We have preset for {}".format(plugin_name))
print(f">>> We have preset for {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))
print(f" - setting `{option}`: `{value}`")
setattr(cls, option, value)

@classmethod
Expand All @@ -79,7 +83,6 @@ def has_valid_extension(cls, repre_entity):
Returns:
bool: Representation has valid extension
"""

if "*" in cls.extensions:
return True

Expand Down Expand Up @@ -124,36 +127,69 @@ def is_compatible_loader(cls, context):
"""

plugin_repre_names = cls.get_representations()
plugin_product_types = cls.product_types

# If the product base type isn't defined on the loader plugin,
# then we will use the product types.
plugin_product_filter = cls.product_base_types
if plugin_product_filter is None:
plugin_product_filter = cls.product_types

if plugin_product_filter:
plugin_product_filter = set(plugin_product_filter)

repre_entity = context.get("representation")
product_entity = context["product"]

# If no representation names, product types or extensions are defined
# then loader is not compatible with any context.
if (
not plugin_repre_names
or not plugin_product_types
or not plugin_product_filter
or not cls.extensions
):
return False

repre_entity = context.get("representation")
# If no representation entity is provided then loader is not
# compatible with context.
if not repre_entity:
return False

# Check the compatibility with the representation names.
plugin_repre_names = set(plugin_repre_names)
if (
"*" not in plugin_repre_names
and repre_entity["name"] not in plugin_repre_names
):
return False

# Check the compatibility with the extension of the representation.
if not cls.has_valid_extension(repre_entity):
return False

plugin_product_types = set(plugin_product_types)
if "*" in plugin_product_types:
product_type = product_entity.get("productType")
product_base_type = product_entity.get("productBaseType")

# Use product base type if defined, otherwise use product type.
product_filter = product_base_type
# If there is no product base type defined in the product entity,
# then we will use the product type.
if product_filter is None:
product_filter = product_type

# If wildcard is used in product types or base types,
# then we will consider the loader compatible with any product type.
if "*" in plugin_product_filter:
return True

product_entity = context["product"]
product_type = product_entity["productType"]
# compatibility with legacy loader
if cls.product_base_types is None and product_base_type:
cls.log.error(
f"Loader {cls.__name__} is doesn't specify "
"`product_base_types` but product entity has "
f"`productBaseType` defined as `{product_base_type}`. "
)

return product_type in plugin_product_types
return product_filter in plugin_product_filter

@classmethod
def get_representations(cls):
Expand Down Expand Up @@ -208,19 +244,17 @@ def remove(self, container):
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'.
"""Returns static (cls) options or could collect from 'contexts'.

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

Expand Down Expand Up @@ -347,10 +381,8 @@ def discover_loader_plugins(project_name=None):
plugin.apply_settings(project_settings)
except Exception:
log.warning(
"Failed to apply settings to loader {}".format(
plugin.__name__
),
exc_info=True,
f"Failed to apply settings to loader {plugin.__name__}",
exc_info=True
)
compatible_hooks = []
for hook_cls in sorted_hooks:
Expand Down
112 changes: 78 additions & 34 deletions client/ayon_core/tools/loader/abstract.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Abstract base classes for loader tool."""
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import List
from typing import Any, List, Optional

from ayon_core.lib.attribute_definitions import (
AbstractAttrDef,
serialize_attr_defs,
deserialize_attr_defs,
serialize_attr_defs,
)


Expand All @@ -13,10 +16,10 @@ class ProductTypeItem:

Args:
name (str): Product type name.
icon (dict[str, Any]): Product type icon definition.
icon (dict[str, str]): Product type icon definition.
"""

def __init__(self, name, icon):
def __init__(self, name: str, icon: dict[str, str]):
self.name = name
self.icon = icon

Expand All @@ -31,15 +34,50 @@ def from_data(cls, data):
return cls(**data)


class ProductBaseTypeItem:
"""Item representing the product base type."""

def __init__(self, name: str, icon: dict[str, str]):
"""Initialize product base type item."""
self.name = name
self.icon = icon

def to_data(self) -> dict[str, Any]:
"""Convert item to data dictionary.

Returns:
dict[str, Any]: Data representation of the item.

"""
return {
"name": self.name,
"icon": self.icon,
}

@classmethod
def from_data(
cls, data: dict[str, Any]) -> ProductBaseTypeItem:
"""Create item from data dictionary.

Args:
data (dict[str, Any]): Data to create item from.

Returns:
ProductBaseTypeItem: Item created from the provided data.

"""
return cls(**data)


class ProductItem:
"""Product item with it versions.

Args:
product_id (str): Product id.
product_type (str): Product type.
product_name (str): Product name.
product_icon (dict[str, Any]): Product icon definition.
product_type_icon (dict[str, Any]): Product type icon definition.
product_icon (dict[str, str]): Product icon definition.
product_type_icon (dict[str, str]): Product type icon definition.
product_in_scene (bool): Is product in scene (only when used in DCC).
group_name (str): Group name.
folder_id (str): Folder id.
Expand All @@ -49,35 +87,41 @@ class ProductItem:

def __init__(
self,
product_id,
product_type,
product_name,
product_icon,
product_type_icon,
product_in_scene,
group_name,
folder_id,
folder_label,
version_items,
product_id: str,
product_type: str,
product_base_type: str,
product_name: str,
product_icon: dict[str, str],
product_type_icon: dict[str, str],
product_base_type_icon: dict[str, str],
group_name: str,
folder_id: str,
folder_label: str,
version_items: dict[str, VersionItem],
product_in_scene: bool,
):
self.product_id = product_id
self.product_type = product_type
self.product_base_type = product_base_type
self.product_name = product_name
self.product_icon = product_icon
self.product_type_icon = product_type_icon
self.product_base_type_icon = product_base_type_icon
self.product_in_scene = product_in_scene
F438 self.group_name = group_name
self.folder_id = folder_id
self.folder_label = folder_label
self.version_items = version_items

def to_data(self):
def to_data(self) -> dict[str, Any]:
return {
"product_id": self.product_id,
"product_type": self.product_type,
"product_base_type": self.product_base_type,
"product_name": self.product_name,
"product_icon": self.product_icon,
"product_type_icon": self.product_type_icon,
"product_base_type_icon": self.product_base_type_icon,
"product_in_scene": self.product_in_scene,
"group_name": self.group_name,
"folder_id": self.folder_id,
Expand Down Expand Up @@ -124,21 +168,21 @@ class VersionItem:

def __init__(
self,
version_id,
version,
is_hero,
product_id,
task_id,
thumbnail_id,
published_time,
author,
status,
frame_range,
duration,
handles,
step,
comment,
source,
version_id: str,
version: int,
is_hero: bool,
product_id: str,
task_id: Optional[str],
thumbnail_id: Optional[str],
published_time: Optional[str],
author: Optional[str],
status: Optional[str],
frame_range: Optional[str],
duration: Optional[int],
handles: Optional[str],
step: Optional[int],
comment: Optional[str],
source: Optional[str],
):
self.version_id = version_id
self.product_id = product_id
Expand Down Expand Up @@ -198,7 +242,7 @@ def __ge__(self, other):
def __le__(self, other):
return self.__eq__(other) or self.__lt__(other)

def to_data(self):
def to_data(self) -> dict[str, Any]:
return {
"version_id": self.version_id,
"product_id": self.product_id,
Expand All @@ -218,7 +262,7 @@ def to_data(self):
}

@classmethod
def from_data(cls, data):
def from_data(cls, data: dict[str, Any]) -> VersionItem:
return cls(**data)


Expand Down
Loading
Loading
0