mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
♻️ change pydantic models to pure dataclasses
This commit is contained in:
parent
50e390e612
commit
534be2c64e
12 changed files with 174 additions and 126 deletions
|
|
@ -184,10 +184,13 @@ to different packages based on their use:
|
|||
| | Overscan | holds overscan/underscan information (added pixels to bottom/sides)
|
||||
| | UDIM | Representation is UDIM tile set
|
||||
|
||||
Traits are [Pydantic models](https://docs.pydantic.dev/latest/) with optional
|
||||
Traits are Python data classes with optional
|
||||
validation and helper methods. If they implement `TraitBase.validate(Representation)` method, they can validate against all other traits
|
||||
in the representation if needed. They can also implement pydantic form of
|
||||
data validators.
|
||||
in the representation if needed.
|
||||
|
||||
> [!NOTE]
|
||||
> They could be easily converted to [Pydantic models](https://docs.pydantic.dev/latest/) but since this must run in diverse Python environments inside DCC, we cannot
|
||||
> easily resolve pydantic-core dependency (as it is binary written in Rust).
|
||||
|
||||
> [!NOTE]
|
||||
> Every trait has id, name and some human readable description. Every trait
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"""Color management related traits."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .trait import TraitBase
|
||||
|
||||
|
||||
@dataclass
|
||||
class ColorManaged(TraitBase):
|
||||
"""Color managed trait.
|
||||
|
||||
|
|
@ -24,9 +24,7 @@ class ColorManaged(TraitBase):
|
|||
|
||||
id: ClassVar[str] = "ayon.color.ColorManaged.v1"
|
||||
name: ClassVar[str] = "ColorManaged"
|
||||
color_space: str
|
||||
description: ClassVar[str] = "Color Managed trait."
|
||||
color_space: str = Field(
|
||||
...,
|
||||
description="Color space."
|
||||
)
|
||||
config: Optional[str] = Field(default=None, description="Color config.")
|
||||
persistent: ClassVar[bool] = True
|
||||
config: Optional[str] = None
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ from __future__ import annotations
|
|||
|
||||
import contextlib
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
# TC003 is there because Path in TYPECHECKING will fail in tests
|
||||
from pathlib import Path # noqa: TC003
|
||||
from typing import ClassVar, Generator, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .representation import Representation
|
||||
from .temporal import FrameRanged, Handles, Sequence
|
||||
from .trait import (
|
||||
|
|
@ -21,6 +20,7 @@ from .two_dimensional import UDIM
|
|||
from .utils import get_sequence_from_files
|
||||
|
||||
|
||||
@dataclass
|
||||
class MimeType(TraitBase):
|
||||
"""MimeType trait model.
|
||||
|
||||
|
|
@ -40,9 +40,11 @@ class MimeType(TraitBase):
|
|||
name: ClassVar[str] = "MimeType"
|
||||
description: ClassVar[str] = "MimeType Trait Model"
|
||||
id: ClassVar[str] = "ayon.content.MimeType.v1"
|
||||
mime_type: str = Field(..., title="Mime Type")
|
||||
persistent: ClassVar[bool] = True
|
||||
mime_type: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class LocatableContent(TraitBase):
|
||||
"""LocatableContent trait model.
|
||||
|
||||
|
|
@ -57,15 +59,18 @@ class LocatableContent(TraitBase):
|
|||
description (str): Trait description.
|
||||
id (str): id should be namespaced trait name with version
|
||||
location (str): Location.
|
||||
is_templated (Optional[bool]): Is the location templated? Default is None.
|
||||
"""
|
||||
|
||||
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(default=None, title="Is Templated")
|
||||
persistent: ClassVar[bool] = True
|
||||
location: str
|
||||
is_templated: Optional[bool] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileLocation(TraitBase):
|
||||
"""FileLocation trait model.
|
||||
|
||||
|
|
@ -78,18 +83,20 @@ class FileLocation(TraitBase):
|
|||
description (str): Trait description.
|
||||
id (str): id should be namespaced trait name with version
|
||||
file_path (str): File path.
|
||||
file_size (int): File size in bytes.
|
||||
file_hash (str): File hash.
|
||||
file_size (Optional[int]): File size in bytes.
|
||||
file_hash (Optional[str]): File hash.
|
||||
"""
|
||||
|
||||
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: Optional[int] = Field(default=None, title="File Size")
|
||||
file_hash: Optional[str] = Field(default=None, title="File Hash")
|
||||
persistent: ClassVar[bool] = True
|
||||
file_path: Path
|
||||
file_size: Optional[int] = None
|
||||
file_hash: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileLocations(TraitBase):
|
||||
"""FileLocation trait model.
|
||||
|
||||
|
|
@ -108,7 +115,8 @@ class FileLocations(TraitBase):
|
|||
name: ClassVar[str] = "FileLocations"
|
||||
description: ClassVar[str] = "FileLocations Trait Model"
|
||||
id: ClassVar[str] = "ayon.content.FileLocations.v1"
|
||||
file_paths: list[FileLocation] = Field(..., title="File Path")
|
||||
persistent: ClassVar[bool] = True
|
||||
file_paths: list[FileLocation]
|
||||
|
||||
def get_files(self) -> Generator[Path, None, None]:
|
||||
"""Get all file paths from the trait.
|
||||
|
|
@ -340,6 +348,7 @@ class FileLocations(TraitBase):
|
|||
return frame_start_with_handles, frame_end_with_handles
|
||||
|
||||
|
||||
@dataclass
|
||||
class RootlessLocation(TraitBase):
|
||||
"""RootlessLocation trait model.
|
||||
|
||||
|
|
@ -363,9 +372,11 @@ class RootlessLocation(TraitBase):
|
|||
name: ClassVar[str] = "RootlessLocation"
|
||||
description: ClassVar[str] = "RootlessLocation Trait Model"
|
||||
id: ClassVar[str] = "ayon.content.RootlessLocation.v1"
|
||||
rootless_path: str = Field(..., title="File Path")
|
||||
persistent: ClassVar[bool] = True
|
||||
rootless_path: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Compressed(TraitBase):
|
||||
"""Compressed trait model.
|
||||
|
||||
|
|
@ -386,9 +397,11 @@ class Compressed(TraitBase):
|
|||
name: ClassVar[str] = "Compressed"
|
||||
description: ClassVar[str] = "Compressed Trait"
|
||||
id: ClassVar[str] = "ayon.content.Compressed.v1"
|
||||
compression_type: str = Field(..., title="Compression Type")
|
||||
persistent: ClassVar[bool] = True
|
||||
compression_type: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Bundle(TraitBase):
|
||||
"""Bundle trait model.
|
||||
|
||||
|
|
@ -424,8 +437,8 @@ class Bundle(TraitBase):
|
|||
name: ClassVar[str] = "Bundle"
|
||||
description: ClassVar[str] = "Bundle Trait"
|
||||
id: ClassVar[str] = "ayon.content.Bundle.v1"
|
||||
items: list[list[TraitBase]] = Field(
|
||||
..., title="Bundles of traits")
|
||||
persistent: ClassVar[bool] = True
|
||||
items: list[list[TraitBase]]
|
||||
|
||||
def to_representations(self) -> Generator[Representation]:
|
||||
"""Convert bundle to representations.
|
||||
|
|
@ -438,6 +451,7 @@ class Bundle(TraitBase):
|
|||
yield Representation(name=f"{self.name} {idx}", traits=item)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Fragment(TraitBase):
|
||||
"""Fragment trait model.
|
||||
|
||||
|
|
@ -466,4 +480,5 @@ class Fragment(TraitBase):
|
|||
name: ClassVar[str] = "Fragment"
|
||||
description: ClassVar[str] = "Fragment Trait"
|
||||
id: ClassVar[str] = "ayon.content.Fragment.v1"
|
||||
parent: str = Field(..., title="Parent Representation Id")
|
||||
persistent: ClassVar[bool] = True
|
||||
parent: str
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"""Cryptography traits."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .trait import TraitBase
|
||||
|
||||
|
||||
@dataclass
|
||||
class DigitallySigned(TraitBase):
|
||||
"""Digitally signed trait.
|
||||
|
||||
|
|
@ -20,25 +20,22 @@ class DigitallySigned(TraitBase):
|
|||
id: ClassVar[str] = "ayon.cryptography.DigitallySigned.v1"
|
||||
name: ClassVar[str] = "DigitallySigned"
|
||||
description: ClassVar[str] = "Digitally signed trait."
|
||||
persistent: ClassVar[bool] = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class PGPSigned(DigitallySigned):
|
||||
"""PGP signed trait.
|
||||
|
||||
This trait holds PGP (RFC-4880) signed data.
|
||||
|
||||
Attributes:
|
||||
signature (str): PGP signature.
|
||||
signed_data (str): Signed data.
|
||||
clear_text (str): Clear text.
|
||||
"""
|
||||
|
||||
id: ClassVar[str] = "ayon.cryptography.PGPSigned.v1"
|
||||
name: ClassVar[str] = "PGPSigned"
|
||||
description: ClassVar[str] = "PGP signed trait."
|
||||
signed_data: str = Field(
|
||||
...,
|
||||
description="Signed data."
|
||||
)
|
||||
clear_text: Optional[str] = Field(
|
||||
None,
|
||||
description="Clear text."
|
||||
)
|
||||
persistent: ClassVar[bool] = True
|
||||
signed_data: str
|
||||
clear_text: Optional[str] = None
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
"""Lifecycle traits."""
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from .trait import TraitBase, TraitValidationError
|
||||
|
||||
|
||||
@dataclass
|
||||
class Transient(TraitBase):
|
||||
"""Transient trait model.
|
||||
|
||||
|
|
@ -19,6 +21,7 @@ class Transient(TraitBase):
|
|||
name: ClassVar[str] = "Transient"
|
||||
description: ClassVar[str] = "Transient Trait Model"
|
||||
id: ClassVar[str] = "ayon.lifecycle.Transient.v1"
|
||||
persistent: ClassVar[bool] = True # see note in Persistent
|
||||
|
||||
def validate_trait(self, representation) -> None: # noqa: ANN001
|
||||
"""Validate representation is not Persistent.
|
||||
|
|
@ -26,14 +29,16 @@ class Transient(TraitBase):
|
|||
Args:
|
||||
representation (Representation): Representation model.
|
||||
|
||||
Returns:
|
||||
bool: True if representation is valid, False otherwise.
|
||||
Raises:
|
||||
TraitValidationError: If representation is marked as both
|
||||
|
||||
"""
|
||||
if representation.contains_trait(Persistent):
|
||||
msg = "Representation is marked as both Persistent and Transient."
|
||||
raise TraitValidationError(self.name, msg)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Persistent(TraitBase):
|
||||
"""Persistent trait model.
|
||||
|
||||
|
|
@ -50,6 +55,10 @@ class Persistent(TraitBase):
|
|||
name: ClassVar[str] = "Persistent"
|
||||
description: ClassVar[str] = "Persistent Trait Model"
|
||||
id: ClassVar[str] = "ayon.lifecycle.Persistent.v1"
|
||||
# note that this affects persistence of the trait itself, not
|
||||
# the representation. This is a class variable, so it is shared
|
||||
# among all instances of the class.
|
||||
persistent: bool = True
|
||||
|
||||
def validate_trait(self, representation) -> None: # noqa: ANN001
|
||||
"""Validate representation is not Transient.
|
||||
|
|
@ -57,6 +66,9 @@ class Persistent(TraitBase):
|
|||
Args:
|
||||
representation (Representation): Representation model.
|
||||
|
||||
Raises:
|
||||
TraitValidationError: If representation is marked as both
|
||||
|
||||
"""
|
||||
if representation.contains_trait(Transient):
|
||||
msg = "Representation is marked as both Persistent and Transient."
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"""Metadata traits."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, List, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .trait import TraitBase
|
||||
|
||||
|
||||
@dataclass
|
||||
class Tagged(TraitBase):
|
||||
"""Tagged trait model.
|
||||
|
||||
|
|
@ -27,9 +27,11 @@ class Tagged(TraitBase):
|
|||
name: ClassVar[str] = "Tagged"
|
||||
description: ClassVar[str] = "Tagged Trait Model"
|
||||
id: ClassVar[str] = "ayon.meta.Tagged.v1"
|
||||
tags: List[str] = Field(..., title="Tags")
|
||||
persistent: ClassVar[bool] = True
|
||||
tags: List[str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class TemplatePath(TraitBase):
|
||||
"""TemplatePath trait model.
|
||||
|
||||
|
|
@ -51,10 +53,12 @@ class TemplatePath(TraitBase):
|
|||
name: ClassVar[str] = "TemplatePath"
|
||||
description: ClassVar[str] = "Template Path Trait Model"
|
||||
id: ClassVar[str] = "ayon.meta.TemplatePath.v1"
|
||||
template: str = Field(..., title="Template Path")
|
||||
data: dict = Field(..., title="Formatting Data")
|
||||
persistent: ClassVar[bool] = True
|
||||
template: str
|
||||
data: dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class Variant(TraitBase):
|
||||
"""Variant trait model.
|
||||
|
||||
|
|
@ -75,9 +79,11 @@ class Variant(TraitBase):
|
|||
name: ClassVar[str] = "Variant"
|
||||
description: ClassVar[str] = "Variant Trait Model"
|
||||
id: ClassVar[str] = "ayon.meta.Variant.v1"
|
||||
variant: str = Field(..., title="Variant")
|
||||
persistent: ClassVar[bool] = True
|
||||
variant: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class KeepOriginalLocation(TraitBase):
|
||||
"""Keep files in its original location.
|
||||
|
||||
|
|
@ -88,9 +94,10 @@ class KeepOriginalLocation(TraitBase):
|
|||
name: ClassVar[str] = "KeepOriginalLocation"
|
||||
description: ClassVar[str] = "Keep Original Location Trait Model"
|
||||
id: ClassVar[str] = "ayon.meta.KeepOriginalLocation.v1"
|
||||
persistent: bool = Field(default=False, title="Persistent")
|
||||
persistent: ClassVar[bool] = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class KeepOriginalName(TraitBase):
|
||||
"""Keep files in its original name.
|
||||
|
||||
|
|
@ -101,9 +108,10 @@ class KeepOriginalName(TraitBase):
|
|||
name: ClassVar[str] = "KeepOriginalName"
|
||||
description: ClassVar[str] = "Keep Original Name Trait Model"
|
||||
id: ClassVar[str] = "ayon.meta.KeepOriginalName.v1"
|
||||
persistent: bool = Field(default=False, title="Persistent")
|
||||
persistent: ClassVar[bool] = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class SourceApplication(TraitBase):
|
||||
"""Metadata about the source (producing) application.
|
||||
|
||||
|
|
@ -115,22 +123,26 @@ class SourceApplication(TraitBase):
|
|||
Note that this is not really connected to any logic in
|
||||
ayon-applications addon.
|
||||
|
||||
Attributes:
|
||||
application (str): Application name.
|
||||
variant (str): Application variant.
|
||||
version (str): Application version.
|
||||
platform (str): Platform name (Windows, darwin, etc.).
|
||||
host_name (str): AYON host name if applicable.
|
||||
"""
|
||||
|
||||
name: ClassVar[str] = "SourceApplication"
|
||||
description: ClassVar[str] = "Source Application Trait Model"
|
||||
id: ClassVar[str] = "ayon.meta.SourceApplication.v1"
|
||||
application: str = Field(..., title="Application Name")
|
||||
variant: Optional[str] = Field(
|
||||
None, title="Application Variant (e.g. Pro)")
|
||||
version: Optional[str] = Field(
|
||||
None, title="Application Version")
|
||||
platform: Optional[str] = Field(
|
||||
None, title="Platform Name (e.g. Windows)")
|
||||
host_name: Optional[str] = Field(
|
||||
None, title="AYON host Name if applicable")
|
||||
persistent: ClassVar[bool] = True
|
||||
application: str
|
||||
variant: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
platform: Optional[str] = None
|
||||
host_name: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class IntendedUse(TraitBase):
|
||||
"""Intended use of the representation.
|
||||
|
||||
|
|
@ -138,9 +150,13 @@ class IntendedUse(TraitBase):
|
|||
can be used in cases, where the other traits are not enough to
|
||||
describe the intended use. For example txt file with tracking
|
||||
points can be used as corner pin in After Effect but not in Nuke.
|
||||
"""
|
||||
|
||||
Attributes:
|
||||
use (str): Intended use description.
|
||||
|
||||
"""
|
||||
name: ClassVar[str] = "IntendedUse"
|
||||
description: ClassVar[str] = "Intended Use Trait Model"
|
||||
id: ClassVar[str] = "ayon.meta.IntendedUse.v1"
|
||||
use: str = Field(..., title="Intended Use")
|
||||
persistent: ClassVar[bool] = True
|
||||
use: str
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ from __future__ import annotations
|
|||
|
||||
import contextlib
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from re import Pattern
|
||||
from typing import TYPE_CHECKING, ClassVar, Optional
|
||||
|
||||
import clique
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from .trait import MissingTraitError, TraitBase, TraitValidationError
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ class GapPolicy(Enum):
|
|||
black = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class FrameRanged(TraitBase):
|
||||
"""Frame ranged trait model.
|
||||
|
||||
|
|
@ -70,16 +71,16 @@ class FrameRanged(TraitBase):
|
|||
name: ClassVar[str] = "FrameRanged"
|
||||
description: ClassVar[str] = "Frame Ranged Trait"
|
||||
id: ClassVar[str] = "ayon.time.FrameRanged.v1"
|
||||
frame_start: int = Field(
|
||||
..., title="Start Frame")
|
||||
frame_end: int = Field(
|
||||
..., title="Frame Start")
|
||||
frame_in: Optional[int] = Field(default=None, title="In Frame")
|
||||
frame_out: Optional[int] = Field(default=None, title="Out Frame")
|
||||
frames_per_second: str = Field(..., title="Frames Per Second")
|
||||
step: Optional[int] = Field(default=1, title="Step")
|
||||
persistent: ClassVar[bool] = True
|
||||
frame_start: int
|
||||
frame_end: int
|
||||
frame_in: Optional[int] = None
|
||||
frame_out: Optional[int] = None
|
||||
frames_per_second: str = None
|
||||
step: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Handles(TraitBase):
|
||||
"""Handles trait model.
|
||||
|
||||
|
|
@ -98,14 +99,13 @@ class Handles(TraitBase):
|
|||
name: ClassVar[str] = "Handles"
|
||||
description: ClassVar[str] = "Handles Trait"
|
||||
id: ClassVar[str] = "ayon.time.Handles.v1"
|
||||
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")
|
||||
persistent: ClassVar[bool] = True
|
||||
inclusive: Optional[bool] = False
|
||||
frame_start_handle: Optional[int] = None
|
||||
frame_end_handle: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Sequence(TraitBase):
|
||||
"""Sequence trait model.
|
||||
|
||||
|
|
@ -130,15 +130,12 @@ class Sequence(TraitBase):
|
|||
name: ClassVar[str] = "Sequence"
|
||||
description: ClassVar[str] = "Sequence Trait Model"
|
||||
id: ClassVar[str] = "ayon.time.Sequence.v1"
|
||||
gaps_policy: Optional[GapPolicy] = Field(
|
||||
default=GapPolicy.forbidden, title="Gaps Policy")
|
||||
frame_padding: int = Field(..., title="Frame Padding")
|
||||
frame_regex: Optional[Pattern] = Field(
|
||||
default=None, title="Frame Regex")
|
||||
frame_spec: Optional[str] = Field(default=None,
|
||||
title="Frame Specification")
|
||||
persistent: ClassVar[bool] = True
|
||||
frame_padding: int
|
||||
gaps_policy: Optional[GapPolicy] = GapPolicy.forbidden
|
||||
frame_regex: Optional[Pattern] = None
|
||||
frame_spec: Optional[str] = None
|
||||
|
||||
@field_validator("frame_regex")
|
||||
@classmethod
|
||||
def validate_frame_regex(
|
||||
cls, v: Optional[Pattern]
|
||||
|
|
@ -426,15 +423,22 @@ class Sequence(TraitBase):
|
|||
|
||||
|
||||
# Do we need one for drop and non-drop frame?
|
||||
@dataclass
|
||||
class SMPTETimecode(TraitBase):
|
||||
"""SMPTE Timecode trait model."""
|
||||
"""SMPTE Timecode trait model.
|
||||
|
||||
Attributes:
|
||||
timecode (str): SMPTE Timecode HH:MM:SS:FF
|
||||
"""
|
||||
|
||||
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")
|
||||
persistent: ClassVar[bool] = True
|
||||
timecode: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Static(TraitBase):
|
||||
"""Static time trait.
|
||||
|
||||
|
|
@ -444,3 +448,4 @@ class Static(TraitBase):
|
|||
name: ClassVar[str] = "Static"
|
||||
description: ClassVar[str] = "Static Time Trait"
|
||||
id: ClassVar[str] = "ayon.time.Static.v1"
|
||||
persistent: ClassVar[bool] = True
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"""3D traits."""
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .trait import TraitBase
|
||||
|
||||
|
||||
@dataclass
|
||||
class Spatial(TraitBase):
|
||||
"""Spatial trait model.
|
||||
|
||||
|
|
@ -29,11 +29,13 @@ class Spatial(TraitBase):
|
|||
id: ClassVar[str] = "ayon.3d.Spatial.v1"
|
||||
name: ClassVar[str] = "Spatial"
|
||||
description: ClassVar[str] = "Spatial trait model."
|
||||
up_axis: str = Field(..., title="Up axis")
|
||||
handedness: str = Field(..., title="Handedness")
|
||||
meters_per_unit: float = Field(..., title="Meters per unit")
|
||||
persistent: ClassVar[bool] = True
|
||||
up_axis: str
|
||||
handedness: str
|
||||
meters_per_unit: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Geometry(TraitBase):
|
||||
"""Geometry type trait model.
|
||||
|
||||
|
|
@ -45,8 +47,10 @@ class Geometry(TraitBase):
|
|||
id: ClassVar[str] = "ayon.3d.Geometry.v1"
|
||||
name: ClassVar[str] = "Geometry"
|
||||
description: ClassVar[str] = "Geometry trait model."
|
||||
persistent: ClassVar[bool] = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class Shader(TraitBase):
|
||||
"""Shader trait model.
|
||||
|
||||
|
|
@ -58,8 +62,10 @@ class Shader(TraitBase):
|
|||
id: ClassVar[str] = "ayon.3d.Shader.v1"
|
||||
name: ClassVar[str] = "Shader"
|
||||
description: ClassVar[str] = "Shader trait model."
|
||||
persistent: ClassVar[bool] = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class Lighting(TraitBase):
|
||||
"""Lighting trait model.
|
||||
|
||||
|
|
@ -71,8 +77,10 @@ class Lighting(TraitBase):
|
|||
id: ClassVar[str] = "ayon.3d.Lighting.v1"
|
||||
name: ClassVar[str] = "Lighting"
|
||||
description: ClassVar[str] = "Lighting trait model."
|
||||
persistent: ClassVar[bool] = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class IESProfile(TraitBase):
|
||||
"""IES profile (IES-LM-64) type trait model.
|
||||
|
||||
|
|
@ -82,3 +90,4 @@ class IESProfile(TraitBase):
|
|||
id: ClassVar[str] = "ayon.3d.IESProfile.v1"
|
||||
name: ClassVar[str] = "IESProfile"
|
||||
description: ClassVar[str] = "IES profile trait model."
|
||||
persistent: ClassVar[bool] = True
|
||||
|
|
|
|||
|
|
@ -3,16 +3,9 @@ from __future__ import annotations
|
|||
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Generic, Optional, TypeVar
|
||||
|
||||
import pydantic.alias_generators
|
||||
from pydantic import (
|
||||
AliasGenerator,
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .representation import Representation
|
||||
|
||||
|
|
@ -20,25 +13,15 @@ if TYPE_CHECKING:
|
|||
T = TypeVar("T", bound="TraitBase")
|
||||
|
||||
|
||||
class TraitBase(ABC, BaseModel):
|
||||
@dataclass
|
||||
class TraitBase(ABC):
|
||||
"""Base trait model.
|
||||
|
||||
This model must be used as a base for all trait models.
|
||||
It is using Pydantic BaseModel for serialization and validation.
|
||||
``id``, ``name``, and ``description`` are abstract attributes that must be
|
||||
implemented in the derived classes.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=AliasGenerator(
|
||||
serialization_alias=pydantic.alias_generators.to_camel,
|
||||
)
|
||||
)
|
||||
|
||||
persistent: bool = Field(
|
||||
default=True, title="Persistent",
|
||||
description="Whether the trait is persistent (integrated) or not.")
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def id(self) -> str:
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, ClassVar, Optional
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from .trait import TraitBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .content import FileLocation, FileLocations
|
||||
|
||||
|
||||
@dataclass
|
||||
class Image(TraitBase):
|
||||
"""Image trait model.
|
||||
|
||||
|
|
@ -26,8 +26,10 @@ class Image(TraitBase):
|
|||
name: ClassVar[str] = "Image"
|
||||
description: ClassVar[str] = "Image Trait"
|
||||
id: ClassVar[str] = "ayon.2d.Image.v1"
|
||||
persistent: ClassVar[bool] = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class PixelBased(TraitBase):
|
||||
"""PixelBased trait model.
|
||||
|
||||
|
|
@ -45,11 +47,13 @@ class PixelBased(TraitBase):
|
|||
name: ClassVar[str] = "PixelBased"
|
||||
description: ClassVar[str] = "PixelBased Trait Model"
|
||||
id: ClassVar[str] = "ayon.2d.PixelBased.v1"
|
||||
display_window_width: int = Field(..., title="Display Window Width")
|
||||
display_window_height: int = Field(..., title="Display Window Height")
|
||||
pixel_aspect_ratio: float = Field(..., title="Pixel Aspect Ratio")
|
||||
persistent: ClassVar[bool] = True
|
||||
display_window_width: int
|
||||
display_window_height: int
|
||||
pixel_aspect_ratio: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Planar(TraitBase):
|
||||
"""Planar trait model.
|
||||
|
||||
|
|
@ -57,7 +61,7 @@ class Planar(TraitBase):
|
|||
|
||||
Todo:
|
||||
* (antirotor): Is this really a planar configuration? As with
|
||||
bitplanes and everything? If it serves as differentiator for
|
||||
bit planes and everything? If it serves as differentiator for
|
||||
Deep images, should it be named differently? Like Raster?
|
||||
|
||||
Attributes:
|
||||
|
|
@ -70,9 +74,11 @@ class Planar(TraitBase):
|
|||
name: ClassVar[str] = "Planar"
|
||||
description: ClassVar[str] = "Planar Trait Model"
|
||||
id: ClassVar[str] = "ayon.2d.Planar.v1"
|
||||
planar_configuration: str = Field(..., title="Planar-based Image")
|
||||
persistent: ClassVar[bool] = True
|
||||
planar_configuration: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Deep(TraitBase):
|
||||
"""Deep trait model.
|
||||
|
||||
|
|
@ -87,8 +93,10 @@ class Deep(TraitBase):
|
|||
name: ClassVar[str] = "Deep"
|
||||
description: ClassVar[str] = "Deep Trait Model"
|
||||
id: ClassVar[str] = "ayon.2d.Deep.v1"
|
||||
persistent: ClassVar[bool] = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class Overscan(TraitBase):
|
||||
"""Overscan trait model.
|
||||
|
||||
|
|
@ -108,12 +116,14 @@ class Overscan(TraitBase):
|
|||
name: ClassVar[str] = "Overscan"
|
||||
description: ClassVar[str] = "Overscan Trait"
|
||||
id: ClassVar[str] = "ayon.2d.Overscan.v1"
|
||||
left: int = Field(..., title="Left Overscan")
|
||||
right: int = Field(..., title="Right Overscan")
|
||||
top: int = Field(..., title="Top Overscan")
|
||||
bottom: int = Field(..., title="Bottom Overscan")
|
||||
persistent: ClassVar[bool] = True
|
||||
left: int
|
||||
right: int
|
||||
top: int
|
||||
bottom: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class UDIM(TraitBase):
|
||||
"""UDIM trait model.
|
||||
|
||||
|
|
@ -124,16 +134,18 @@ class UDIM(TraitBase):
|
|||
description (str): Trait description.
|
||||
id (str): id should be namespaced trait name with version
|
||||
udim (int): UDIM value.
|
||||
udim_regex (str): UDIM regex.
|
||||
"""
|
||||
|
||||
name: ClassVar[str] = "UDIM"
|
||||
description: ClassVar[str] = "UDIM Trait"
|
||||
id: ClassVar[str] = "ayon.2d.UDIM.v1"
|
||||
udim: list[int] = Field(..., title="UDIM")
|
||||
udim_regex: Optional[str] = Field(
|
||||
default=r"(?:\.|_)(?P<udim>\d+)\.\D+\d?$", title="UDIM Regex")
|
||||
persistent: ClassVar[bool] = True
|
||||
udim: list[int]
|
||||
udim_regex: Optional[str] = r"(?:\.|_)(?P<udim>\d+)\.\D+\d?$"
|
||||
|
||||
@field_validator("udim_regex")
|
||||
# field validator for udim_regex - this works in pydantic model v2 but not
|
||||
# with the pure data classes
|
||||
@classmethod
|
||||
def validate_frame_regex(cls, v: Optional[str]) -> Optional[str]:
|
||||
"""Validate udim regex.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ pyblish-base = "^1.8.11"
|
|||
speedcopy = "^2.1"
|
||||
six = "^1.15"
|
||||
qtawesome = "0.7.3"
|
||||
pydantic = "^2.9.2"
|
||||
|
||||
[ayon.runtimeDependencies]
|
||||
aiohttp-middlewares = "^2.0.0"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ readme = "README.md"
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9.1,<3.10"
|
||||
pydantic = "^2.9.2"
|
||||
pre-commit = "^4.0.0"
|
||||
clique = "^2"
|
||||
pyblish-base = "^1.8"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue