From b0bd488e4cede9996c81910bcbf0b28616832d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 4 Nov 2024 19:22:02 +0100 Subject: [PATCH] :recycle: add traits and docstrings sync traits declared in #909 --- client/ayon_core/pipeline/traits/__init__.py | 32 ++++++- client/ayon_core/pipeline/traits/color.py | 31 ++++++ client/ayon_core/pipeline/traits/content.py | 95 +++++++++++++++++-- .../ayon_core/pipeline/traits/cryptography.py | 42 ++++++++ client/ayon_core/pipeline/traits/lifecycle.py | 31 +++++- client/ayon_core/pipeline/traits/meta.py | 13 ++- .../pipeline/traits/three_dimensional.py | 60 +++++++++++- client/ayon_core/pipeline/traits/time.py | 74 ++++++++++++--- .../pipeline/traits/two_dimensional.py | 21 ++-- 9 files changed, 355 insertions(+), 44 deletions(-) create mode 100644 client/ayon_core/pipeline/traits/color.py create mode 100644 client/ayon_core/pipeline/traits/cryptography.py diff --git a/client/ayon_core/pipeline/traits/__init__.py b/client/ayon_core/pipeline/traits/__init__.py index bc9b1d69de..a12c55e41a 100644 --- a/client/ayon_core/pipeline/traits/__init__.py +++ b/client/ayon_core/pipeline/traits/__init__.py @@ -1,15 +1,26 @@ """Trait classes for the pipeline.""" +from .color import ColorManaged from .content import ( Bundle, Compressed, FileLocation, + Fragment, + LocatableContent, MimeType, RootlessLocation, ) +from .cryptography import DigitallySigned, GPGSigned from .lifecycle import Persistent, Transient from .meta import Tagged, TemplatePath -from .three_dimensional import Spatial -from .time import Clip, GapPolicy, Sequence, SMPTETimecode +from .three_dimensional import Geometry, IESProfile, Lighting, Shader, Spatial +from .time import ( + FrameRanged, + GapPolicy, + Handles, + Sequence, + SMPTETimecode, + Static, +) from .trait import Representation, TraitBase from .two_dimensional import ( UDIM, @@ -31,6 +42,15 @@ "FileLocation", "MimeType", "RootlessLocation", + "Fragment", + "LocatableContent", + + # color + "ColorManaged", + + # cryptography + "DigitallySigned", + "GPGSigned", # life cycle "Persistent", @@ -50,10 +70,16 @@ "UDIM", # three-dimensional + "Geometry", + "IESProfile", + "Lighting", + "Shader", "Spatial", # time - "Clip", + "FrameRanged", + "Static", + "Handles", "GapPolicy", "Sequence", "SMPTETimecode", diff --git a/client/ayon_core/pipeline/traits/color.py b/client/ayon_core/pipeline/traits/color.py new file mode 100644 index 0000000000..9d1fcd913c --- /dev/null +++ b/client/ayon_core/pipeline/traits/color.py @@ -0,0 +1,31 @@ +"""Color management related traits.""" +from __future__ import annotations + +from typing import ClassVar, Optional + +from pydantic import Field + +from .trait import TraitBase + + +class ColorManaged(TraitBase): + """Color managed trait. + + Holds color management information. Can be used with Image related + traits to define color space and config. + + Sync with OpenAssetIO MediaCreation Traits. + + Attributes: + color_space (str): An OCIO colorspace name available + in the "current" OCIO context. + config (str): An OCIO config name defining color space. + """ + id: ClassVar[str] = "ayon.color.ColorManaged.v1" + name: ClassVar[str] = "ColorManaged" + description: ClassVar[str] = "Color Managed trait." + color_space: str = Field( + ..., + description="Color space." + ) + config: Optional[str] = Field(None, description="Color config.") diff --git a/client/ayon_core/pipeline/traits/content.py b/client/ayon_core/pipeline/traits/content.py index 480d1657f5..3f08cc2214 100644 --- a/client/ayon_core/pipeline/traits/content.py +++ b/client/ayon_core/pipeline/traits/content.py @@ -13,13 +13,17 @@ class MimeType(TraitBase): """MimeType trait model. - This model represents a mime type trait. + This model represents a mime type trait. For example, image/jpeg. + It is used to describe the type of content in a representation regardless + of the file extension. + + For more information, see RFC 2046 and RFC 4288 (and related RFCs). Attributes: name (str): Trait name. description (str): Trait description. id (str): id should be namespaced trait name with version - mime_type (str): Mime type. + mime_type (str): Mime type like image/jpeg. """ @@ -28,10 +32,35 @@ class MimeType(TraitBase): id: ClassVar[str] = "ayon.content.MimeType.v1" mime_type: str = Field(..., title="Mime Type") -class FileLocation(TraitBase): +class LocatableContent(TraitBase): + """LocatableContent trait model. + + This model represents a locatable content trait. Locatable content + is content that has a location. It doesn't have to be a file - it could + be a URL or some other location. + + Sync with OpenAssetIO MediaCreation Traits. + + Attributes: + name (str): Trait name. + description (str): Trait description. + id (str): id should be namespaced trait name with version + location (str): Location. + + """ + + name: ClassVar[str] = "LocatableContent" + description: ClassVar[str] = "LocatableContent Trait Model" + id: ClassVar[str] = "ayon.content.LocatableContent.v1" + location: str = Field(..., title="Location") + is_templated: Optional[bool] = Field(None, title="Is Templated") + +class FileLocation(LocatableContent): """FileLocation trait model. - This model represents a file location trait. + This model represents a file path. It is a specialization of the + LocatableContent trait. It is adding optional file size and file hash + for easy access to file information. Attributes: name (str): Trait name. @@ -46,14 +75,22 @@ class FileLocation(TraitBase): name: ClassVar[str] = "FileLocation" description: ClassVar[str] = "FileLocation Trait Model" id: ClassVar[str] = "ayon.content.FileLocation.v1" - file_path: Path = Field(..., title="File Path") - file_size: int = Field(..., title="File Size") + file_path: Path = Field(..., title="File Path", alias="location") + file_size: int = Field(None, title="File Size") file_hash: Optional[str] = Field(None, title="File Hash") class RootlessLocation(TraitBase): """RootlessLocation trait model. - This model represents a rootless location trait. + RootlessLocation trait is a trait that represents a file path that is + without specific root. To obtain absolute path, the root needs to be + resolved by AYON. Rootless path can be used on multiple platforms. + + Example:: + + RootlessLocation( + rootless_path="{root[work]}/project/asset/asset.jpg" + ) Attributes: name (str): Trait name. @@ -72,7 +109,12 @@ class RootlessLocation(TraitBase): class Compressed(TraitBase): """Compressed trait model. - This model represents a compressed trait. + This trait can hold information about compressed content. What type + of compression is used. + + Example:: + + Compressed("gzip") Attributes: name (str): Trait name. @@ -96,6 +138,29 @@ class Bundle(TraitBase): a collection of representations that are part of a single entity. + Example:: + + Bundle( + items=[ + [ + Representation( + traits=[ + MimeType(mime_type="image/jpeg"), + FileLocation(file_path="/path/to/file.jpg") + ] + ) + ], + [ + Representation( + traits=[ + MimeType(mime_type="image/png"), + FileLocation(file_path="/path/to/file.png") + ] + ) + ] + ] + ) + Attributes: name (str): Trait name. description (str): Trait description. @@ -119,7 +184,19 @@ class Fragment(TraitBase): """Fragment trait model. This model represents a fragment trait. A fragment is a part of - a larger entity that is represented by a representation. + a larger entity that is represented by another representation. + + Example:: + + main_representation = Representation(name="parent", + traits=[], + ) + fragment_representation = Representation( + name="fragment", + traits=[ + Fragment(parent=main_representation.id), + ] + ) Attributes: name (str): Trait name. diff --git a/client/ayon_core/pipeline/traits/cryptography.py b/client/ayon_core/pipeline/traits/cryptography.py new file mode 100644 index 0000000000..4fa9e64c2f --- /dev/null +++ b/client/ayon_core/pipeline/traits/cryptography.py @@ -0,0 +1,42 @@ +"""Cryptography traits.""" +from __future__ import annotations + +from typing import ClassVar, Optional + +from pydantic import Field + +from .trait import TraitBase + + +class DigitallySigned(TraitBase): + """Digitally signed trait. + + This type trait means that the data is digitally signed. + + Attributes: + signature (str): Digital signature. + """ + id: ClassVar[str] = "ayon.cryptography.DigitallySigned.v1" + name: ClassVar[str] = "DigitallySigned" + description: ClassVar[str] = "Digitally signed trait." + + +class GPGSigned(DigitallySigned): + """GPG signed trait. + + This trait holds GPG signed data. + + Attributes: + signature (str): GPG signature. + """ + id: ClassVar[str] = "ayon.cryptography.GPGSigned.v1" + name: ClassVar[str] = "GPGSigned" + description: ClassVar[str] = "GPG signed trait." + signed_data: str = Field( + ..., + description="Signed data." + ) + clear_text: Optional[str] = Field( + None, + description="Clear text." + ) diff --git a/client/ayon_core/pipeline/traits/lifecycle.py b/client/ayon_core/pipeline/traits/lifecycle.py index 2877a4d396..fed620da9b 100644 --- a/client/ayon_core/pipeline/traits/lifecycle.py +++ b/client/ayon_core/pipeline/traits/lifecycle.py @@ -1,30 +1,44 @@ """Lifecycle traits.""" from typing import ClassVar +from . import Representation from .trait import TraitBase class Transient(TraitBase): """Transient trait model. - This model represents a transient trait. + Transient trait marks representation as transient. Such representations + are not persisted in the system. Attributes: name (str): Trait name. description (str): Trait description. id (str): id should be namespaced trait name with version - tags (List[str]): Tags. """ name: ClassVar[str] = "Transient" description: ClassVar[str] = "Transient Trait Model" id: ClassVar[str] = "ayon.lifecycle.Transient.v1" + def validate(self, representation: Representation) -> bool: + """Validate representation is not Persistent. + + Args: + representation (Representation): Representation model. + + Returns: + bool: True if representation is valid, False otherwise. + """ + return not representation.contains_trait(Persistent) + class Persistent(TraitBase): """Persistent trait model. - This model represents a persistent trait. + Persistent trait is opposite to transient trait. It marks representation + as persistent. Such representations are persisted in the system (e.g. in + the database). Attributes: name (str): Trait name. @@ -35,3 +49,14 @@ class Persistent(TraitBase): name: ClassVar[str] = "Persistent" description: ClassVar[str] = "Persistent Trait Model" id: ClassVar[str] = "ayon.lifecycle.Persistent.v1" + + def validate(self, representation: Representation) -> bool: + """Validate representation is not Transient. + + Args: + representation (Representation): Representation model. + + Returns: + bool: True if representation is valid, False otherwise. + """ + return not representation.contains_trait(Transient) diff --git a/client/ayon_core/pipeline/traits/meta.py b/client/ayon_core/pipeline/traits/meta.py index 36ad5e8b0f..745b065798 100644 --- a/client/ayon_core/pipeline/traits/meta.py +++ b/client/ayon_core/pipeline/traits/meta.py @@ -9,7 +9,11 @@ class Tagged(TraitBase): """Tagged trait model. - This model represents a tagged trait. + This trait can hold list of tags. + + Example:: + + Tagged(tags=["tag1", "tag2"]) Attributes: name (str): Trait name. @@ -28,12 +32,17 @@ class TemplatePath(TraitBase): """TemplatePath trait model. This model represents a template path with formatting data. + Template path can be Anatomy template and data is used to format it. + + Example:: + + TemplatePath(template="path/{key}/file", data={"key": "to"}) Attributes: name (str): Trait name. description (str): Trait description. id (str): id should be namespaced trait name with version - template_path (str): Template path. + template (str): Template path. data (dict[str]): Formatting data. """ diff --git a/client/ayon_core/pipeline/traits/three_dimensional.py b/client/ayon_core/pipeline/traits/three_dimensional.py index 0638700ab7..eb27797ed2 100644 --- a/client/ayon_core/pipeline/traits/three_dimensional.py +++ b/client/ayon_core/pipeline/traits/three_dimensional.py @@ -1,4 +1,4 @@ -"""Two-dimensional image traits.""" +"""3D traits.""" from typing import ClassVar from pydantic import Field @@ -9,6 +9,17 @@ class Spatial(TraitBase): """Spatial trait model. + Trait describing spatial information. Up axis valid strings are + "Y", "Z", "X". Handedness valid strings are "left", "right". Meters per + unit is a float value. + + Example:: + + Spatial(up_axis="Y", handedness="right", meters_per_unit=1.0) + + Todo: + * Add value validation for up_axis and handedness. + Attributes: up_axis (str): Up axis. handedness (str): Handedness. @@ -21,3 +32,50 @@ class Spatial(TraitBase): up_axis: str = Field(..., title="Up axis") handedness: str = Field(..., title="Handedness") meters_per_unit: float = Field(..., title="Meters per unit") + + +class Geometry(TraitBase): + """Geometry type trait model. + + Type trait for geometry data. + + Sync with OpenAssetIO MediaCreation Traits. + """ + + id: ClassVar[str] = "ayon.3d.Geometry.v1" + name: ClassVar[str] = "Geometry" + description: ClassVar[str] = "Geometry trait model." + +class Shader(TraitBase): + """Shader trait model. + + Type trait for shader data. + + Sync with OpenAssetIO MediaCreation Traits. + """ + + id: ClassVar[str] = "ayon.3d.Shader.v1" + name: ClassVar[str] = "Shader" + description: ClassVar[str] = "Shader trait model." + +class Lighting(TraitBase): + """Lighting trait model. + + Type trait for lighting data. + + Sync with OpenAssetIO MediaCreation Traits. + """ + + id: ClassVar[str] = "ayon.3d.Lighting.v1" + name: ClassVar[str] = "Lighting" + description: ClassVar[str] = "Lighting trait model." + +class IESProfile(TraitBase): + """IES profile (IES-LM-64) type trait model. + + Sync with OpenAssetIO MediaCreation Traits. + """ + + id: ClassVar[str] = "ayon.3d.IESProfile.v1" + name: ClassVar[str] = "IESProfile" + description: ClassVar[str] = "IES profile trait model." diff --git a/client/ayon_core/pipeline/traits/time.py b/client/ayon_core/pipeline/traits/time.py index 840462faeb..05bcb1602c 100644 --- a/client/ayon_core/pipeline/traits/time.py +++ b/client/ayon_core/pipeline/traits/time.py @@ -12,6 +12,8 @@ class GapPolicy(Enum): """Gap policy enumeration. + This type defines how to handle gaps in sequence. + Attributes: forbidden (int): Gaps are forbidden. missing (int): Gaps are interpreted as missing frames. @@ -23,11 +25,12 @@ class GapPolicy(Enum): hold = auto() black = auto() +class FrameRanged(TraitBase): + """Frame ranged trait model. -class Clip(TraitBase): - """Clip trait model. + Model representing a frame ranged trait. - Model representing a clip trait. + Sync with OpenAssetIO MediaCreation Traits. Attributes: name (str): Trait name. @@ -35,6 +38,37 @@ class Clip(TraitBase): id (str): id should be namespaced trait name with version frame_start (int): Frame start. frame_end (int): Frame end. + frame_in (int): Frame in. + frame_out (int): Frame out. + frames_per_second (int): Frames per second. + step (int): Step. + + """ + name: ClassVar[str] = "FrameRanged" + description: ClassVar[str] = "Frame Ranged Trait" + id: ClassVar[str] = "ayon.time.FrameRanged.v1" + frame_start: int = Field( + ..., title="Start Frame", alias="start_frame") + frame_end: int = Field( + ..., title="Frame Start", alias="end_frame") + frame_in: int = Field(..., title="In Frame", alias="in_frame") + frame_out: int = Field(..., title="Out Frame", alias="out_frame") + frames_per_second: int = Field( + ..., title="Frames Per Second", alias="fps") + step: Optional[int] = Field(1, title="Step") + + +class Handles(TraitBase): + """Handles trait model. + + Handles define the range of frames that are included or excluded + from the sequence. + + Attributes: + name (str): Trait name. + description (str): Trait description. + id (str): id should be namespaced trait name with version + inclusive (bool): Handles are inclusive. frame_start_handle (int): Frame start handle. frame_end_handle (int): Frame end handle. @@ -42,22 +76,24 @@ class Clip(TraitBase): name: ClassVar[str] = "Clip" description: ClassVar[str] = "Clip Trait" id: ClassVar[str] = "ayon.time.Clip.v1" - frame_start: int = Field(..., title="Frame Start") - frame_end: int = Field(..., title="Frame End") - frame_start_handle: Optional[int] = Field(0, title="Frame Start Handle") - frame_end_handle: Optional[int] = Field(0, title="Frame End Handle") - -class Sequence(Clip): + inclusive: Optional[bool] = Field( + False, title="Handles are inclusive") # noqa: FBT003 + frame_start_handle: Optional[int] = Field( + 0, title="Frame Start Handle") + frame_end_handle: Optional[int] = Field( + 0, title="Frame End Handle") + +class Sequence(FrameRanged, Handles): """Sequence trait model. - This model represents a sequence trait. Based on the Clip trait, - adding handling for steps, gaps policy and frame padding. + This model represents a sequence trait. Based on the FrameRanged trait + and Handles, adding support for gaps policy, frame padding and frame + list specification. Regex is used to match frame numbers. Attributes: name (str): Trait name. description (str): Trait description. id (str): id should be namespaced trait name with version - step (int): Frame step. gaps_policy (GapPolicy): Gaps policy - how to handle gaps in sequence. frame_padding (int): Frame padding. @@ -70,7 +106,6 @@ class Sequence(Clip): name: ClassVar[str] = "Sequence" description: ClassVar[str] = "Sequence Trait Model" id: ClassVar[str] = "ayon.time.Sequence.v1" - step: Optional[int] = Field(1, title="Step") gaps_policy: GapPolicy = Field( GapPolicy.forbidden, title="Gaps Policy") frame_padding: int = Field(..., title="Frame Padding") @@ -80,8 +115,19 @@ class Sequence(Clip): # Do we need one for drop and non-drop frame? class SMPTETimecode(TraitBase): - """Timecode trait model.""" + """SMPTE Timecode trait model.""" name: ClassVar[str] = "Timecode" description: ClassVar[str] = "SMPTE Timecode Trait" id: ClassVar[str] = "ayon.time.SMPTETimecode.v1" timecode: str = Field(..., title="SMPTE Timecode HH:MM:SS:FF") + + +class Static(TraitBase): + """Static time trait. + + Used to define static time (single frame). + + """ + name: ClassVar[str] = "Static" + description: ClassVar[str] = "Static Time Trait" + id: ClassVar[str] = "ayon.time.Static.v1" diff --git a/client/ayon_core/pipeline/traits/two_dimensional.py b/client/ayon_core/pipeline/traits/two_dimensional.py index 69e129ec41..93d21a9bc3 100644 --- a/client/ayon_core/pipeline/traits/two_dimensional.py +++ b/client/ayon_core/pipeline/traits/two_dimensional.py @@ -9,7 +9,7 @@ class Image(TraitBase): """Image trait model. - This model represents an image trait. + Type trait model for image. Attributes: name (str): Trait name. @@ -26,7 +26,7 @@ class Image(TraitBase): class PixelBased(TraitBase): """PixelBased trait model. - This model represents a pixel based trait. + Pixel related trait for image data. Attributes: name (str): Trait name. @@ -51,9 +51,10 @@ class Planar(TraitBase): This model represents an Image with planar configuration. - TODO (antirotor): Is this really a planar configuration? As with - bitplanes and everything? If it serves as differentiator for - Deep images, should it be named differently? Like Raster? + Todo: + * (antirotor): Is this really a planar configuration? As with + bitplanes and everything? If it serves as differentiator for + Deep images, should it be named differently? Like Raster? Attributes: name (str): Trait name. @@ -72,29 +73,25 @@ class Planar(TraitBase): class Deep(TraitBase): """Deep trait model. - This model represents a deep image trait. + Type trait model for deep EXR images. Attributes: name (str): Trait name. description (str): Trait description. id (str): id should be namespaced trait name with version - deep_data_type (str): Deep data type. """ name: ClassVar[str] = "Deep" description: ClassVar[str] = "Deep Trait Model" id: ClassVar[str] = "ayon.2d.Deep.v1" - deep_data_type: str = Field(..., title="Deep Data Type") - - - class Overscan(TraitBase): """Overscan trait model. - This model represents an overscan (or underscan) trait. + This model represents an overscan (or underscan) trait. Defines the + extra pixels around the image. Attributes: name (str): Trait name.