🐛 fix template data

This commit is contained in:
Ondrej Samohel 2024-12-10 23:02:10 +01:00
parent 365575604c
commit 1dd3c00eb5
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
2 changed files with 219 additions and 41 deletions

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import contextlib import contextlib
import copy import copy
from copy import deepcopy
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any, List from typing import TYPE_CHECKING, Any, List
@ -47,8 +48,8 @@ if TYPE_CHECKING:
from ayon_core.pipeline import Anatomy from ayon_core.pipeline import Anatomy
from ayon_core.pipeline.anatomy.templates import ( from ayon_core.pipeline.anatomy.templates import (
TemplateItem as AnatomyTemplateItem, TemplateItem as AnatomyTemplateItem, AnatomyStringTemplate,
) )
@dataclass(frozen=True) @dataclass(frozen=True)
@ -203,23 +204,43 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
"Instance has no persistent representations. Skipping") "Instance has no persistent representations. Skipping")
return return
# 3) get template and template data
template: str = self.get_publish_template(instance)
# 4) initialize OperationsSession()
op_session = OperationsSession() op_session = OperationsSession()
# 5) Prepare product
product_entity = self.prepare_product(instance, op_session) product_entity = self.prepare_product(instance, op_session)
# 6) Prepare version
version_entity = self.prepare_version( version_entity = self.prepare_version(
instance, op_session, product_entity instance, op_session, product_entity
) )
instance.data["versionEntity"] = version_entity instance.data["versionEntity"] = version_entity
instance_template_data: dict[str, str] = {} transfers = self.get_transfers_from_representations(
instance, representations)
def get_transfers_from_representations(
self,
instance: pyblish.api.Instance,
representations: list[Representation]) -> list[TransferItem]:
"""Get transfers from representations.
This method will go through all representations and prepare transfers
based on the traits they contain. First it will validate the
representation, and then it will prepare template data for the
representation. It specifically handles FileLocations, FileLocation,
Bundle, Sequence and UDIM traits.
Args:
instance (pyblish.api.Instance): Instance to process.
representations (list[Representation]): List of representations.
Returns:
list[TransferItem]: List of transfers.
Raises:
PublishError: If representation is invalid.
"""
template: str = self.get_publish_template(instance)
instance_template_data: dict[str, str] = {}
transfers: list[TransferItem] = [] transfers: list[TransferItem] = []
# prepare template and data to format it # prepare template and data to format it
for representation in representations: for representation in representations:
@ -235,6 +256,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
template_data = self.get_template_data_from_representation( template_data = self.get_template_data_from_representation(
representation, instance) representation, instance)
# add instance based template data # add instance based template data
template_data.update(instance_template_data) template_data.update(instance_template_data)
# treat Variant as `output` in template data # treat Variant as `output` in template data
@ -246,7 +268,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
template_item = TemplateItem( template_item = TemplateItem(
anatomy=instance.context.data["anatomy"], anatomy=instance.context.data["anatomy"],
template=template, template=template,
template_data=template_data, template_data=copy.deepcopy(template_data),
template_object=self.get_publish_template_object(instance), template_object=self.get_publish_template_object(instance),
) )
@ -273,7 +295,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
self.get_transfers_from_bundle( self.get_transfers_from_bundle(
representation, template_item, transfers representation, template_item, transfers
) )
return transfers
def _get_relative_to_root_original_dirname( def _get_relative_to_root_original_dirname(
self, instance: pyblish.api.Instance) -> str: self, instance: pyblish.api.Instance) -> str:
@ -668,6 +690,8 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
""" """
template_data = copy.deepcopy(instance.data["anatomyData"]) template_data = copy.deepcopy(instance.data["anatomyData"])
template_data["representation"] = representation.name template_data["representation"] = representation.name
template_data["version"] = instance.data["version"]
template_data["hierarchy"] = instance.data["hierarchy"]
# add colorspace data to template data # add colorspace data to template data
if representation.contains_trait(ColorManaged): if representation.contains_trait(ColorManaged):
@ -770,23 +794,26 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
if template_padding > dst_padding: if template_padding > dst_padding:
dst_padding = template_padding dst_padding = template_padding
# add template path and the data to resolve it
representation.add_trait(TemplatePath(
template=template_item.template,
data=template_item.template_data
))
# go through all frames in the sequence # go through all frames in the sequence
# find their corresponding file locations # find their corresponding file locations
# format their template and add them to transfers # format their template and add them to transfers
for frame in frames: for frame in frames:
template_item.template_data["frame"] = frame
template_filled = path_template_object.format_strict(
template_item.template_data
)
file_loc: FileLocation = representation.get_trait( file_loc: FileLocation = representation.get_trait(
FileLocations).get_file_location_for_frame( FileLocations).get_file_location_for_frame(
frame, sequence) frame, sequence)
template_item.template_data["frame"] = frame
template_item.template_data["ext"] = (
file_loc.file_path.suffix
)
template_filled = path_template_object.format_strict(
template_item.template_data
)
# add used values to the template data
used_values: dict = template_filled.used_values
template_item.template_data.update(used_values)
transfers.append( transfers.append(
TransferItem( TransferItem(
source=file_loc.file_path, source=file_loc.file_path,
@ -799,6 +826,12 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
) )
) )
# add template path and the data to resolve it
representation.add_trait(TemplatePath(
template=template_item.template,
data=template_item.template_data
))
@staticmethod @staticmethod
def get_transfers_from_udim( def get_transfers_from_udim(
@ -819,13 +852,23 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
""" """
udim: UDIM = representation.get_trait(UDIM) udim: UDIM = representation.get_trait(UDIM)
path_template_object: AnatomyStringTemplate = (
template_item.template_object["path"]
)
for file_loc in representation.get_trait( for file_loc in representation.get_trait(
FileLocations).file_paths: FileLocations).file_paths:
template_item.template_data["udim"] = ( template_item.template_data["udim"] = (
udim.get_udim_from_file_location(file_loc) udim.get_udim_from_file_location(file_loc)
) )
template_filled = template_item.template.format(
**template_item.template_data) template_filled = path_template_object.format_strict(
template_item.template_data
)
# add used values to the template data
used_values: dict = template_filled.used_values
template_item.template_data.update(used_values)
transfers.append( transfers.append(
TransferItem( TransferItem(
source=file_loc.file_path, source=file_loc.file_path,
@ -861,7 +904,12 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
template_item (TemplateItem): Template item. template_item (TemplateItem): Template item.
""" """
path_template_object = template_item.template_object["path"] path_template_object: AnatomyStringTemplate = (
template_item.template_object["path"]
)
template_item.template_data["ext"] = (
representation.get_trait(FileLocation).file_path.suffix
)
template_item.template_data.pop("frame", None) template_item.template_data.pop("frame", None)
with contextlib.suppress(MissingTraitError): with contextlib.suppress(MissingTraitError):
udim = representation.get_trait(UDIM) udim = representation.get_trait(UDIM)
@ -870,6 +918,11 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
template_filled = path_template_object.format_strict( template_filled = path_template_object.format_strict(
template_item.template_data template_item.template_data
) )
# add used values to the template data
used_values: dict = template_filled.used_values
template_item.template_data.update(used_values)
file_loc: FileLocation = representation.get_trait(FileLocation) file_loc: FileLocation = representation.get_trait(FileLocation)
transfers.append( transfers.append(
TransferItem( TransferItem(
@ -878,7 +931,7 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
size=file_loc.file_size, size=file_loc.file_size,
checksum=file_loc.file_hash, checksum=file_loc.file_hash,
template=template_item.template, template=template_item.template,
template_data=template_item.template_data.copy(), template_data=template_item.template_data,
representation=representation, representation=representation,
) )
) )
@ -928,3 +981,4 @@ class IntegrateTraits(pyblish.api.InstancePlugin):
IntegrateTraits.get_transfers_from_bundle( IntegrateTraits.get_transfers_from_bundle(
sub_representation, template_item, transfers sub_representation, template_item, transfers
) )

View file

@ -2,10 +2,16 @@
from __future__ import annotations from __future__ import annotations
import base64 import base64
import time
from pathlib import Path from pathlib import Path
import pyblish.api import pyblish.api
import pytest import pytest
import pytest_ayon
from ayon_api.operations import (
OperationsSession,
)
from ayon_core.pipeline.anatomy import Anatomy
from ayon_core.pipeline.traits import ( from ayon_core.pipeline.traits import (
FileLocation, FileLocation,
FileLocations, FileLocations,
@ -18,6 +24,7 @@ from ayon_core.pipeline.traits import (
Sequence, Sequence,
Transient, Transient,
) )
from ayon_core.pipeline.version_start import get_versioning_start
# Tagged, # Tagged,
# TemplatePath, # TemplatePath,
@ -26,6 +33,8 @@ from ayon_core.settings import get_project_settings
PNG_FILE_B64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR4AWNgAAAAAgABc3UBGAAAAABJRU5ErkJggg==" # noqa: E501 PNG_FILE_B64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR4AWNgAAAAAgABc3UBGAAAAABJRU5ErkJggg==" # noqa: E501
SEQUENCE_LENGTH = 10 SEQUENCE_LENGTH = 10
CURRENT_TIME = time.time()
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def single_file(tmp_path_factory: pytest.TempPathFactory) -> Path: def single_file(tmp_path_factory: pytest.TempPathFactory) -> Path:
@ -48,7 +57,7 @@ def sequence_files(tmp_path_factory: pytest.TempPathFactory) -> list[Path]:
@pytest.fixture() @pytest.fixture()
def mock_context( def mock_context(
project: object, project: pytest_ayon.ProjectInfo,
single_file: Path, single_file: Path,
sequence_files: list[Path]) -> pyblish.api.Context: sequence_files: list[Path]) -> pyblish.api.Context:
"""Return a mock instance. """Return a mock instance.
@ -56,18 +65,6 @@ def mock_context(
This is mocking pyblish context for testing. It is using real AYON project This is mocking pyblish context for testing. It is using real AYON project
thanks to the ``project`` fixture. thanks to the ``project`` fixture.
This returns following data::
project_name: str
project_code: str
project_root_folders: dict[str, str]
folder: IdNamePair
task: IdNamePair
product: IdNamePair
version: IdNamePair
representations: List[IdNamePair]
links: List[str]
Args: Args:
project (object): The project info. It is `ProjectInfo` object project (object): The project info. It is `ProjectInfo` object
returned by pytest fixture. returned by pytest fixture.
@ -75,25 +72,68 @@ def mock_context(
sequence_files (list[Path]): The paths to a sequence of image files. sequence_files (list[Path]): The paths to a sequence of image files.
""" """
anatomy = Anatomy(project.project_name)
context = pyblish.api.Context() context = pyblish.api.Context()
context.data["projectName"] = project.project_name context.data["projectName"] = project.project_name
context.data["hostName"] = "test_host" context.data["hostName"] = "test_host"
context.data["project_settings"] = get_project_settings( context.data["project_settings"] = get_project_settings(
project.project_name) project.project_name)
context.data["anatomy"] = anatomy
context.data["time"] = CURRENT_TIME
context.data["user"] = "test_user"
context.data["machine"] = "test_machine"
context.data["fps"] = 25
instance = context.create_instance("mock_instance") instance = context.create_instance("mock_instance")
instance.data["source"] = "test_source"
instance.data["families"] = ["render"]
instance.data["anatomyData"] = { instance.data["anatomyData"] = {
"project": project.project_name, "project": {
"name": project.project_name,
"code": project.project_code
},
"task": { "task": {
"name": project.task.name, "name": project.task.name,
"type": "test" # pytest-ayon doesn't return the task type yet "type": "test" # pytest-ayon doesn't return the task type yet
} },
"folder": {
"name": project.folder.name,
"type": "test" # pytest-ayon doesn't return the folder type yet
},
"product": {
"name": project.product.name,
"type": "test" # pytest-ayon doesn't return the product type yet
},
} }
instance.data["folderEntity"] = project.folder_entity
instance.data["productType"] = "test_product" instance.data["productType"] = "test_product"
instance.data["productName"] = project.product.name
instance.data["anatomy"] = anatomy
instance.data["comment"] = "test_comment"
instance.data["integrate"] = True instance.data["integrate"] = True
instance.data["farm"] = False instance.data["farm"] = False
parents = project.folder_entity["path"].lstrip("/").split("/")
hierarchy = ""
if parents:
hierarchy = "/".join(parents)
instance.data["hierarchy"] = hierarchy
version_number = get_versioning_start(
context.data["projectName"],
instance.context.data["hostName"],
task_name=project.task.name,
task_type="test",
product_type=instance.data["productType"],
product_name=instance.data["productName"]
)
instance.data["version"] = version_number
_file_size = len(base64.b64decode(PNG_FILE_B64)) _file_size = len(base64.b64decode(PNG_FILE_B64))
file_locations = [ file_locations = [
FileLocation( FileLocation(
@ -121,7 +161,7 @@ def mock_context(
), ),
Sequence( Sequence(
frame_padding=4, frame_padding=4,
frame_regex=r"^img\.(\d{4})\.png$", frame_regex=r"^img\.(?P<frame>\d{4})\.png$",
), ),
FileLocations( FileLocations(
file_paths=file_locations, file_paths=file_locations,
@ -176,3 +216,87 @@ def test_filter_lifecycle() -> None:
assert len(filtered) == 1 assert len(filtered) == 1
assert filtered[0] == persistent_representation assert filtered[0] == persistent_representation
def test_prepare_product(
project: pytest_ayon.ProjectInfo,
mock_context: pyblish.api.Context) -> None:
"""Test prepare_product."""
integrator = IntegrateTraits()
op_session = OperationsSession()
product = integrator.prepare_product(mock_context[0], op_session)
assert product == {
"attrib": {},
"data": {
"families": ["default", "render"],
},
"folderId": project.folder_entity["id"],
"name": "renderMain",
"productType": "test_product",
"id": project.product_entity["id"],
}
def test_prepare_version(
project: pytest_ayon.ProjectInfo,
mock_context: pyblish.api.Context) -> None:
"""Test prepare_version."""
integrator = IntegrateTraits()
op_session = OperationsSession()
product = integrator.prepare_product(mock_context[0], op_session)
version = integrator.prepare_version(
mock_context[0], op_session , product)
assert version == {
"attrib": {
"comment": "test_comment",
"families": ["default", "render"],
"fps": 25,
"machine": "test_machine",
"source": "test_source",
},
"data": {
"author": "test_user",
"time": CURRENT_TIME,
},
"id": project.version_entity["id"],
"productId": project.product_entity["id"],
"version": 1,
}
def test_get_transfers_from_representation(
mock_context: pyblish.api.Context) -> None:
"""Test get_transfers_from_representation."""
integrator = IntegrateTraits()
instance = mock_context[0]
representations: list[Representation] = instance.data[
"representations_with_traits"]
transfers = integrator.get_transfers_from_representations(
instance, representations)
assert transfers == [
{
"file_path": Path("test"),
"file_size": 1234,
"traits": [
"Persistent",
"Image",
"MimeType"
]
},
{
"file_path": Path("test"),
"file_size": 1234,
"traits": [
"Persistent",
"FrameRanged",
"Sequence",
"FileLocations",
"Image",
"PixelBased",
"MimeType"
]
}
]