mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge remote-tracking branch 'origin/feature/911-new-traits-based-integrator' into feature/911-new-traits-based-integrator
This commit is contained in:
commit
44f56e23ba
4 changed files with 136 additions and 12 deletions
|
|
@ -391,7 +391,7 @@ class Representation(Generic[T]): # noqa: PLR0904
|
|||
|
||||
"""
|
||||
return {
|
||||
trait_id: trait.model_dump()
|
||||
trait_id: trait.as_dict()
|
||||
for trait_id, trait in self._data.items()
|
||||
if trait and trait_id
|
||||
}
|
||||
|
|
@ -593,10 +593,6 @@ class Representation(Generic[T]): # noqa: PLR0904
|
|||
)
|
||||
raise IncompatibleTraitVersionError(msg) from e
|
||||
|
||||
if requested_version is None:
|
||||
trait_class = e.found_trait
|
||||
requested_version = found_version
|
||||
|
||||
if found_version is None:
|
||||
msg = (
|
||||
f"Trait {e.found_trait.id} found with no version, "
|
||||
|
|
@ -604,6 +600,10 @@ class Representation(Generic[T]): # noqa: PLR0904
|
|||
)
|
||||
raise IncompatibleTraitVersionError(msg) from e
|
||||
|
||||
if requested_version is None:
|
||||
trait_class = e.found_trait
|
||||
requested_version = found_version
|
||||
|
||||
if requested_version > found_version:
|
||||
error_msg = (
|
||||
f"Requested trait version {requested_version} is "
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import TYPE_CHECKING, Generic, Optional, TypeVar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -79,6 +79,15 @@ class TraitBase(ABC):
|
|||
"""
|
||||
return re.sub(r"\.v\d+$", "", str(cls.id))
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Return trait as dictionary.
|
||||
|
||||
Returns:
|
||||
dict: Trait as dictionary.
|
||||
|
||||
"""
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class IncompatibleTraitVersionError(Exception):
|
||||
"""Incompatible trait version exception.
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ from __future__ import annotations
|
|||
import contextlib
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, List
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pyblish.api
|
||||
from ayon_api import (
|
||||
|
|
@ -88,6 +89,7 @@ class TransferItem:
|
|||
template: str
|
||||
template_data: dict[str, Any]
|
||||
representation: Representation
|
||||
related_trait: FileLocation
|
||||
|
||||
@staticmethod
|
||||
def get_size(file_path: Path) -> int:
|
||||
|
|
@ -149,7 +151,7 @@ class RepresentationEntity:
|
|||
status: str
|
||||
|
||||
|
||||
def get_instance_families(instance: pyblish.api.Instance) -> List[str]:
|
||||
def get_instance_families(instance: pyblish.api.Instance) -> list[str]:
|
||||
"""Get all families of the instance.
|
||||
|
||||
Todo:
|
||||
|
|
@ -159,7 +161,7 @@ def get_instance_families(instance: pyblish.api.Instance) -> List[str]:
|
|||
instance (pyblish.api.Instance): Instance to get families from.
|
||||
|
||||
Returns:
|
||||
List[str]: List of families.
|
||||
list[str]: List of families.
|
||||
|
||||
"""
|
||||
family = instance.data.get("family")
|
||||
|
|
@ -210,10 +212,39 @@ def get_changed_attributes(
|
|||
return changes
|
||||
|
||||
|
||||
def prepare_for_json(data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Prepare data for JSON serialization.
|
||||
|
||||
If there are values that json cannot serialize, this function will
|
||||
convert them to strings.
|
||||
|
||||
Args:
|
||||
data (dict[str, Any]): Data to prepare.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Prepared data.
|
||||
|
||||
Raises:
|
||||
TypeError: If the data cannot be converted to JSON.
|
||||
|
||||
"""
|
||||
prepared = {}
|
||||
for key, value in data.items():
|
||||
if isinstance(value, dict):
|
||||
value = prepare_for_json(value)
|
||||
try:
|
||||
json.dumps(value)
|
||||
except TypeError:
|
||||
value = value.as_posix() if issubclass(
|
||||
value.__class__, Path) else str(value)
|
||||
prepared[key] = value
|
||||
return prepared
|
||||
|
||||
|
||||
class IntegrateTraits(pyblish.api.InstancePlugin):
|
||||
"""Integrate representations with traits."""
|
||||
|
||||
label = "Integrate Asset"
|
||||
label = "Integrate Traits of an Asset"
|
||||
order = pyblish.api.IntegratorOrder
|
||||
log: logging.Logger
|
||||
|
||||
|
|
@ -229,7 +260,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
|
|||
"""
|
||||
# 1) skip farm and integrate == False
|
||||
|
||||
if not instance.data.get("integrate"):
|
||||
if instance.data.get("integrate", True) is False:
|
||||
self.log.debug("Instance is marked to skip integrating. Skipping")
|
||||
return
|
||||
|
||||
|
|
@ -290,6 +321,10 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
|
|||
self.log.debug(
|
||||
"Transferred files %s", [file_transactions.transferred])
|
||||
|
||||
# replace original paths with the destination in traits.
|
||||
for transfer in transfers:
|
||||
transfer.related_trait.file_path = transfer.destination
|
||||
|
||||
# 9) Create representation entities
|
||||
for representation in representations:
|
||||
representation_entity = new_representation_entity(
|
||||
|
|
@ -306,8 +341,14 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
|
|||
)
|
||||
# add traits to representation entity
|
||||
representation_entity["traits"] = representation.traits_as_dict()
|
||||
op_session.create_entity(
|
||||
project_name=instance.context.data["projectName"],
|
||||
entity_type="representation",
|
||||
data=prepare_for_json(representation_entity),
|
||||
)
|
||||
|
||||
# 10) Commit the session to AYON
|
||||
self.log.debug("{}".format(op_session.to_data()))
|
||||
op_session.commit()
|
||||
|
||||
def get_transfers_from_representations(
|
||||
|
|
@ -811,7 +852,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
|
|||
template_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
template_data["representation"] = representation.name
|
||||
template_data["version"] = instance.data["version"]
|
||||
template_data["hierarchy"] = instance.data["hierarchy"]
|
||||
# template_data["hierarchy"] = instance.data["hierarchy"]
|
||||
|
||||
# add colorspace data to template data
|
||||
if representation.contains_trait(ColorManaged):
|
||||
|
|
@ -944,6 +985,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
|
|||
template=template_item.template,
|
||||
template_data=template_item.template_data,
|
||||
representation=representation,
|
||||
related_trait=file_loc
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1001,6 +1043,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
|
|||
template=template_item.template,
|
||||
template_data=template_item.template_data,
|
||||
representation=representation,
|
||||
related_trait=file_loc
|
||||
)
|
||||
)
|
||||
# add template path and the data to resolve it
|
||||
|
|
@ -1058,6 +1101,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
|
|||
template=template_item.template,
|
||||
template_data=template_item.template_data,
|
||||
representation=representation,
|
||||
related_trait=file_loc
|
||||
)
|
||||
)
|
||||
# add template path and the data to resolve it
|
||||
|
|
|
|||
|
|
@ -237,6 +237,8 @@ def mock_context(
|
|||
return context
|
||||
|
||||
|
||||
|
||||
|
||||
def test_get_template_name(mock_context: pyblish.api.Context) -> None:
|
||||
"""Test get_template_name.
|
||||
|
||||
|
|
@ -252,6 +254,75 @@ def test_get_template_name(mock_context: pyblish.api.Context) -> None:
|
|||
assert template_name == "default"
|
||||
|
||||
|
||||
class TestGetSize:
|
||||
@staticmethod
|
||||
def get_size(file_path: Path) -> int:
|
||||
"""Get size of the file.
|
||||
|
||||
Args:
|
||||
file_path (Path): File path.
|
||||
|
||||
Returns:
|
||||
int: Size of the file.
|
||||
|
||||
"""
|
||||
return file_path.stat().st_size
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_path, expected_size",
|
||||
[
|
||||
(Path("./test_file_1.txt"), 10), # id: happy_path_small_file
|
||||
(Path("./test_file_2.txt"), 1024), # id: happy_path_medium_file
|
||||
(Path("./test_file_3.txt"), 10485760) # id: happy_path_large_file
|
||||
],
|
||||
ids=["happy_path_small_file", "happy_path_medium_file", "happy_path_large_file"]
|
||||
)
|
||||
def test_get_size_happy_path(self, file_path: Path, expected_size: int, tmp_path: Path):
|
||||
# Arrange
|
||||
file_path = tmp_path / file_path
|
||||
file_path.write_bytes(b"\0" * expected_size)
|
||||
|
||||
# Act
|
||||
size = self.get_size(file_path)
|
||||
|
||||
# Assert
|
||||
assert size == expected_size
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_path, expected_size",
|
||||
[
|
||||
(Path("./test_file_empty.txt"), 0) # id: edge_case_empty_file
|
||||
],
|
||||
ids=["edge_case_empty_file"]
|
||||
)
|
||||
def test_get_size_edge_cases(self, file_path: Path, expected_size: int, tmp_path: Path):
|
||||
# Arrange
|
||||
file_path = tmp_path / file_path
|
||||
file_path.touch() # Create an empty file
|
||||
|
||||
# Act
|
||||
size = self.get_size(file_path)
|
||||
|
||||
# Assert
|
||||
assert size == expected_size
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_path, expected_exception",
|
||||
[
|
||||
(Path("./non_existent_file.txt"), FileNotFoundError), # id: error_file_not_found
|
||||
(123, TypeError) # id: error_invalid_input_type
|
||||
],
|
||||
ids=["error_file_not_found", "error_invalid_input_type"]
|
||||
)
|
||||
def test_get_size_error_cases(self, file_path, expected_exception, tmp_path):
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(expected_exception):
|
||||
file_path = tmp_path / file_path
|
||||
self.get_size(file_path)
|
||||
|
||||
|
||||
def test_filter_lifecycle() -> None:
|
||||
"""Test filter_lifecycle."""
|
||||
integrator = IntegrateTraits()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue