Skip to content

traits

Trait classes for the pipeline.

Bundle dataclass

Bases: TraitBase

Bundle trait model.

This model list of independent Representation traits that are bundled together. This is useful for representing a collection of sub-entities that are part of a single entity. You can easily reconstruct representations from the bundle.

Example::

    Bundle(
        items=[
            [
                MimeType(mime_type="image/jpeg"),
                FileLocation(file_path="/path/to/file.jpg")
            ],
            [

                MimeType(mime_type="image/png"),
                FileLocation(file_path="/path/to/file.png")
            ]
        ]
    )

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

items list[list[TraitBase]]

List of representations.

Source code in client/ayon_core/pipeline/traits/content.py
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
@dataclass
class Bundle(TraitBase):
    """Bundle trait model.

    This model list of independent Representation traits
    that are bundled together. This is useful for representing
    a collection of sub-entities that are part of a single
    entity. You can easily reconstruct representations from
    the bundle.

    Example::

            Bundle(
                items=[
                    [
                        MimeType(mime_type="image/jpeg"),
                        FileLocation(file_path="/path/to/file.jpg")
                    ],
                    [

                        MimeType(mime_type="image/png"),
                        FileLocation(file_path="/path/to/file.png")
                    ]
                ]
            )

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with version
        items (list[list[TraitBase]]): List of representations.
    """

    name: ClassVar[str] = "Bundle"
    description: ClassVar[str] = "Bundle Trait"
    id: ClassVar[str] = "ayon.content.Bundle.v1"
    persistent: ClassVar[bool] = True
    items: list[list[TraitBase]]

    def to_representations(self) -> Generator[Representation]:
        """Convert a bundle to representations.

        Yields:
            Representation: Representation of the bundle.

        """
        for idx, item in enumerate(self.items):
            yield Representation(name=f"{self.name} {idx}", traits=item)

to_representations()

Convert a bundle to representations.

Yields:

Name Type Description
Representation Generator[Representation]

Representation of the bundle.

Source code in client/ayon_core/pipeline/traits/content.py
444
445
446
447
448
449
450
451
452
def to_representations(self) -> Generator[Representation]:
    """Convert a bundle to representations.

    Yields:
        Representation: Representation of the bundle.

    """
    for idx, item in enumerate(self.items):
        yield Representation(name=f"{self.name} {idx}", traits=item)

ColorManaged dataclass

Bases: 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:

Name Type Description
color_space str

An OCIO colorspace name available in the "current" OCIO context.

config str

An OCIO config name defining color space.

Source code in client/ayon_core/pipeline/traits/color.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@dataclass
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"
    color_space: str
    description: ClassVar[str] = "Color Managed trait."
    persistent: ClassVar[bool] = True
    config: Optional[str] = None

Compressed dataclass

Bases: TraitBase

Compressed trait model.

This trait can hold information about compressed content. What type of compression is used.

Example::

Compressed("gzip")

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

compression_type str

Compression type.

Source code in client/ayon_core/pipeline/traits/content.py
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
@dataclass
class Compressed(TraitBase):
    """Compressed trait model.

    This trait can hold information about compressed content. What type
    of compression is used.

    Example::

        Compressed("gzip")

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with version
        compression_type (str): Compression type.
    """

    name: ClassVar[str] = "Compressed"
    description: ClassVar[str] = "Compressed Trait"
    id: ClassVar[str] = "ayon.content.Compressed.v1"
    persistent: ClassVar[bool] = True
    compression_type: str

Deep dataclass

Bases: TraitBase

Deep trait model.

Type trait model for deep EXR images.

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with a version

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@dataclass
class Deep(TraitBase):
    """Deep trait model.

    Type trait model for deep EXR images.

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with a version
    """

    name: ClassVar[str] = "Deep"
    description: ClassVar[str] = "Deep Trait Model"
    id: ClassVar[str] = "ayon.2d.Deep.v1"
    persistent: ClassVar[bool] = True

DigitallySigned dataclass

Bases: TraitBase

Digitally signed trait.

This type trait means that the data is digitally signed.

Attributes:

Name Type Description
signature str

Digital signature.

Source code in client/ayon_core/pipeline/traits/cryptography.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@dataclass
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."
    persistent: ClassVar[bool] = True

FileLocation dataclass

Bases: TraitBase

FileLocation trait model.

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 Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

file_path str

File path.

file_size Optional[int]

File size in bytes.

file_hash Optional[str]

File hash.

Source code in client/ayon_core/pipeline/traits/content.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
@dataclass
class FileLocation(TraitBase):
    """FileLocation trait model.

    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.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with version
        file_path (str): File path.
        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"
    persistent: ClassVar[bool] = True
    file_path: Path
    file_size: Optional[int] = None
    file_hash: Optional[str] = None

FileLocations dataclass

Bases: TraitBase

FileLocation trait model.

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 Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

file_paths list of FileLocation

File locations.

Source code in client/ayon_core/pipeline/traits/content.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
@dataclass
class FileLocations(TraitBase):
    """FileLocation trait model.

    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.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with version
        file_paths (list of FileLocation): File locations.

    """

    name: ClassVar[str] = "FileLocations"
    description: ClassVar[str] = "FileLocations Trait Model"
    id: ClassVar[str] = "ayon.content.FileLocations.v1"
    persistent: ClassVar[bool] = True
    file_paths: list[FileLocation]

    def get_files(self) -> Generator[Path, None, None]:
        """Get all file paths from the trait.

        This method will return all file paths from the trait.

        Yields:
            Path: List of file paths.

        """
        for file_location in self.file_paths:
            yield file_location.file_path

    def get_file_location_for_frame(
            self,
            frame: int,
            sequence_trait: Optional[Sequence] = None,
        ) -> Optional[FileLocation]:
        """Get a file location for a frame.

        This method will return the file location for a given frame. If the
        frame is not found in the file paths, it will return None.

        Args:
            frame (int): Frame to get the file location for.
            sequence_trait (Sequence): Sequence trait to get the
                frame range specs from.

        Returns:
            Optional[FileLocation]: File location for the frame.

        """
        frame_regex = re.compile(r"\.(?P<index>(?P<padding>0*)\d+)\.\D+\d?$")
        if sequence_trait and sequence_trait.frame_regex:
            frame_regex = sequence_trait.get_frame_pattern()

        for location in self.file_paths:
            result = re.search(frame_regex, location.file_path.name)
            if result:
                frame_index = int(result.group("index"))
                if frame_index == frame:
                    return location
        return None

    def validate_trait(self, representation: Representation) -> None:
        """Validate the trait.

        This method validates the trait against others in the representation.
        In particular, it checks that the sequence trait is present, and if
        so, it will compare the frame range to the file paths.

        Args:
            representation (Representation): Representation to validate.

        Raises:
            TraitValidationError: If the trait is invalid within the
                representation.

        """
        super().validate_trait(representation)
        if len(self.file_paths) == 0:
            # If there are no file paths, we can't validate
            msg = "No file locations defined (empty list)"
            raise TraitValidationError(self.name, msg)
        if representation.contains_trait(FrameRanged):
            self._validate_frame_range(representation)
        if not representation.contains_trait(Sequence) \
                and not representation.contains_trait(UDIM):
            # we have multiple files, but it is not a sequence
            # or UDIM tile set what is it then? If the files are not related
            # to each other, then this representation is invalid.
            msg = (
                 "Multiple file locations defined, but no Sequence "
                 "or UDIM trait defined. If the files are not related to "
                 "each other, the representation is invalid."
            )
            raise TraitValidationError(self.name, msg)

    def _validate_frame_range(self, representation: Representation) -> None:
        """Validate the frame range against the file paths.

        If the representation contains a FrameRanged trait, this method will
        validate the frame range against the file paths. If the frame range
        does not match the file paths, the trait is invalid. It takes into
        account the Handles and Sequence traits.

        Args:
            representation (Representation): Representation to validate.

        Raises:
            TraitValidationError: If the trait is invalid within the
                representation.

        """
        tmp_frame_ranged: FrameRanged = get_sequence_from_files(
                    [f.file_path for f in self.file_paths])

        frames_from_spec: list[int] = []
        with contextlib.suppress(MissingTraitError):
            sequence: Sequence = representation.get_trait(Sequence)
            frame_regex = sequence.get_frame_pattern()
            if sequence.frame_spec:
                frames_from_spec = sequence.get_frame_list(
                    self, frame_regex)

        frame_start_with_handles, frame_end_with_handles = \
            self._get_frame_info_with_handles(representation, frames_from_spec)

        if frame_start_with_handles \
                and tmp_frame_ranged.frame_start != frame_start_with_handles:
            # If the detected frame range does not match the combined
            # FrameRanged and Handles trait, the
            # trait is invalid.
            msg = (
                f"Frame range defined by {self.name} "
                f"({tmp_frame_ranged.frame_start}-"
                f"{tmp_frame_ranged.frame_end}) "
                "in files does not match "
                "frame range "
                f"({frame_start_with_handles}-"
                f"{frame_end_with_handles}) defined in FrameRanged trait."
            )

            raise TraitValidationError(self.name, msg)

        if frames_from_spec:
            if len(frames_from_spec) != len(self.file_paths):
                # If the number of file paths does not match the frame range,
                # the trait is invalid
                msg = (
                    f"Number of file locations ({len(self.file_paths)}) "
                    "does not match frame range defined by frame spec "
                    "on Sequence trait: "
                    f"({len(frames_from_spec)})"
                )
                raise TraitValidationError(self.name, msg)
            # if there is a frame spec on the Sequence trait,
            # we should not validate the frame range from the files.
            # the rest is validated by Sequence validators.
            return

        length_with_handles: int = (
            frame_end_with_handles - frame_start_with_handles + 1
        )

        if len(self.file_paths) != length_with_handles:
            # If the number of file paths does not match the frame range,
            # the trait is invalid
            msg = (
                f"Number of file locations ({len(self.file_paths)}) "
                "does not match frame range "
                f"({length_with_handles})"
            )
            raise TraitValidationError(self.name, msg)

        frame_ranged: FrameRanged = representation.get_trait(FrameRanged)

        if frame_start_with_handles != tmp_frame_ranged.frame_start or \
                frame_end_with_handles != tmp_frame_ranged.frame_end:
            # If the frame range does not match the FrameRanged trait, the
            # trait is invalid. Note that we don't check the frame rate
            # because it is not stored in the file paths and is not
            # determined by `get_sequence_from_files`.
            msg = (
                "Frame range "
                f"({frame_ranged.frame_start}-{frame_ranged.frame_end}) "
                "in sequence trait does not match "
                "frame range "
                f"({tmp_frame_ranged.frame_start}-"
                f"{tmp_frame_ranged.frame_end}) "
            )
            raise TraitValidationError(self.name, msg)

    @staticmethod
    def _get_frame_info_with_handles(
            representation: Representation,
            frames_from_spec: list[int]) -> tuple[int, int]:
        """Get the frame range with handles from the representation.

        This will return frame start and frame end with handles calculated
        in if there actually is the Handles trait in the representation.

        Args:
            representation (Representation): Representation to get the frame
                range from.
            frames_from_spec (list[int]): List of frames from the frame spec.
                This list is modified in place to take into
                account the handles.

        Mutates:
            frames_from_spec: List of frames from the frame spec.

        Returns:
            tuple[int, int]: Start and end frame with handles.

        """
        frame_start = frame_end = 0
        frame_start_handle = frame_end_handle = 0
        # If there is no sequence trait, we can't validate it
        if frames_from_spec and representation.contains_trait(FrameRanged):
            # if there is no FrameRanged trait (but really there should be)
            # we can use the frame range from the frame spec
            frame_start = min(frames_from_spec)
            frame_end = max(frames_from_spec)

        # Handle the frame range
        with contextlib.suppress(MissingTraitError):
            frame_start = representation.get_trait(FrameRanged).frame_start
            frame_end = representation.get_trait(FrameRanged).frame_end

        # Handle the handles :P
        with contextlib.suppress(MissingTraitError):
            handles: Handles = representation.get_trait(Handles)
            if not handles.inclusive:
                # if handless are exclusive, we need to adjust the frame range
                frame_start_handle = handles.frame_start_handle or 0
                frame_end_handle = handles.frame_end_handle or 0
                if frames_from_spec:
                    frames_from_spec.extend(
                        range(frame_start - frame_start_handle, frame_start)
                    )
                    frames_from_spec.extend(
                        range(frame_end + 1, frame_end_handle + frame_end + 1)
                    )

        frame_start_with_handles = frame_start - frame_start_handle
        frame_end_with_handles = frame_end + frame_end_handle

        return frame_start_with_handles, frame_end_with_handles

get_file_location_for_frame(frame, sequence_trait=None)

Get a file location for a frame.

This method will return the file location for a given frame. If the frame is not found in the file paths, it will return None.

Parameters:

Name Type Description Default
frame int

Frame to get the file location for.

required
sequence_trait Sequence

Sequence trait to get the frame range specs from.

None

Returns:

Type Description
Optional[FileLocation]

Optional[FileLocation]: File location for the frame.

Source code in client/ayon_core/pipeline/traits/content.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
def get_file_location_for_frame(
        self,
        frame: int,
        sequence_trait: Optional[Sequence] = None,
    ) -> Optional[FileLocation]:
    """Get a file location for a frame.

    This method will return the file location for a given frame. If the
    frame is not found in the file paths, it will return None.

    Args:
        frame (int): Frame to get the file location for.
        sequence_trait (Sequence): Sequence trait to get the
            frame range specs from.

    Returns:
        Optional[FileLocation]: File location for the frame.

    """
    frame_regex = re.compile(r"\.(?P<index>(?P<padding>0*)\d+)\.\D+\d?$")
    if sequence_trait and sequence_trait.frame_regex:
        frame_regex = sequence_trait.get_frame_pattern()

    for location in self.file_paths:
        result = re.search(frame_regex, location.file_path.name)
        if result:
            frame_index = int(result.group("index"))
            if frame_index == frame:
                return location
    return None

get_files()

Get all file paths from the trait.

This method will return all file paths from the trait.

Yields:

Name Type Description
Path Path

List of file paths.

Source code in client/ayon_core/pipeline/traits/content.py
122
123
124
125
126
127
128
129
130
131
132
def get_files(self) -> Generator[Path, None, None]:
    """Get all file paths from the trait.

    This method will return all file paths from the trait.

    Yields:
        Path: List of file paths.

    """
    for file_location in self.file_paths:
        yield file_location.file_path

validate_trait(representation)

Validate the trait.

This method validates the trait against others in the representation. In particular, it checks that the sequence trait is present, and if so, it will compare the frame range to the file paths.

Parameters:

Name Type Description Default
representation Representation

Representation to validate.

required

Raises:

Type Description
TraitValidationError

If the trait is invalid within the representation.

Source code in client/ayon_core/pipeline/traits/content.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def validate_trait(self, representation: Representation) -> None:
    """Validate the trait.

    This method validates the trait against others in the representation.
    In particular, it checks that the sequence trait is present, and if
    so, it will compare the frame range to the file paths.

    Args:
        representation (Representation): Representation to validate.

    Raises:
        TraitValidationError: If the trait is invalid within the
            representation.

    """
    super().validate_trait(representation)
    if len(self.file_paths) == 0:
        # If there are no file paths, we can't validate
        msg = "No file locations defined (empty list)"
        raise TraitValidationError(self.name, msg)
    if representation.contains_trait(FrameRanged):
        self._validate_frame_range(representation)
    if not representation.contains_trait(Sequence) \
            and not representation.contains_trait(UDIM):
        # we have multiple files, but it is not a sequence
        # or UDIM tile set what is it then? If the files are not related
        # to each other, then this representation is invalid.
        msg = (
             "Multiple file locations defined, but no Sequence "
             "or UDIM trait defined. If the files are not related to "
             "each other, the representation is invalid."
        )
        raise TraitValidationError(self.name, msg)

Fragment dataclass

Bases: TraitBase

Fragment trait model.

This model represents a fragment trait. A fragment is a part of 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 Type Description
name str

Trait name.

description str

Trait description.

id str

id should be namespaced trait name with version

parent str

Parent representation id.

Source code in client/ayon_core/pipeline/traits/content.py
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
@dataclass
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 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.
        description (str): Trait description.
        id (str): id should be namespaced trait name with version
        parent (str): Parent representation id.
    """

    name: ClassVar[str] = "Fragment"
    description: ClassVar[str] = "Fragment Trait"
    id: ClassVar[str] = "ayon.content.Fragment.v1"
    persistent: ClassVar[bool] = True
    parent: str

FrameRanged dataclass

Bases: TraitBase

Frame ranged trait model.

Model representing a frame-ranged trait.

Sync with OpenAssetIO MediaCreation Traits. For compatibility with OpenAssetIO, we'll need to handle different names of attributes:

* frame_start -> start_frame
* frame_end -> end_frame
...
frames_per_second is a string to allow various precision

formats. FPS is a floating point number, but it can be also represented as a fraction (e.g. "30000/1001") or as a decimal or even as an irrational number. We need to support all these formats. To work with FPS, we'll need some helper function to convert FPS to Decimal from string.

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with a version

frame_start int

Frame start.

frame_end int

Frame end.

frame_in int

Frame in.

frame_out int

Frame out.

frames_per_second str

Frames per second.

step int

Step.

Source code in client/ayon_core/pipeline/traits/temporal.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@dataclass
class FrameRanged(TraitBase):
    """Frame ranged trait model.

    Model representing a frame-ranged trait.

    Sync with OpenAssetIO MediaCreation Traits. For compatibility with
    OpenAssetIO, we'll need to handle different names of attributes:

        * frame_start -> start_frame
        * frame_end -> end_frame
        ...

    Note: frames_per_second is a string to allow various precision
        formats. FPS is a floating point number, but it can be also
        represented as a fraction (e.g. "30000/1001") or as a decimal
        or even as an irrational number. We need to support all these
        formats. To work with FPS, we'll need some helper function
        to convert FPS to Decimal from string.

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with a version
        frame_start (int): Frame start.
        frame_end (int): Frame end.
        frame_in (int): Frame in.
        frame_out (int): Frame out.
        frames_per_second (str): Frames per second.
        step (int): Step.
    """

    name: ClassVar[str] = "FrameRanged"
    description: ClassVar[str] = "Frame Ranged Trait"
    id: ClassVar[str] = "ayon.time.FrameRanged.v1"
    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

GapPolicy

Bases: Enum

Gap policy enumeration.

This type defines how to handle gaps in a sequence.

Attributes:

Name Type Description
forbidden int

Gaps are forbidden.

missing int

Gaps are interpreted as missing frames.

hold int

Gaps are interpreted as hold frames (last existing frames).

black int

Gaps are interpreted as black frames.

Source code in client/ayon_core/pipeline/traits/temporal.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class GapPolicy(Enum):
    """Gap policy enumeration.

    This type defines how to handle gaps in a sequence.

    Attributes:
        forbidden (int): Gaps are forbidden.
        missing (int): Gaps are interpreted as missing frames.
        hold (int): Gaps are interpreted as hold frames (last existing frames).
        black (int): Gaps are interpreted as black frames.
    """

    forbidden = auto()
    missing = auto()
    hold = auto()
    black = auto()

Geometry dataclass

Bases: TraitBase

Geometry type trait model.

Type trait for geometry data.

Sync with OpenAssetIO MediaCreation Traits.

Source code in client/ayon_core/pipeline/traits/three_dimensional.py
38
39
40
41
42
43
44
45
46
47
48
49
50
@dataclass
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."
    persistent: ClassVar[bool] = True

Handles dataclass

Bases: TraitBase

Handles trait model.

Handles define the range of frames that are included or excluded from the sequence.

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with a version

inclusive bool

Handles are inclusive.

frame_start_handle int

Frame start handle.

frame_end_handle int

Frame end handle.

Source code in client/ayon_core/pipeline/traits/temporal.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
@dataclass
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 a namespaced trait name with a version
        inclusive (bool): Handles are inclusive.
        frame_start_handle (int): Frame start handle.
        frame_end_handle (int): Frame end handle.
    """

    name: ClassVar[str] = "Handles"
    description: ClassVar[str] = "Handles Trait"
    id: ClassVar[str] = "ayon.time.Handles.v1"
    persistent: ClassVar[bool] = True
    inclusive: Optional[bool] = False
    frame_start_handle: Optional[int] = None
    frame_end_handle: Optional[int] = None

IESProfile dataclass

Bases: TraitBase

IES profile (IES-LM-64) type trait model.

Sync with OpenAssetIO MediaCreation Traits.

Source code in client/ayon_core/pipeline/traits/three_dimensional.py
83
84
85
86
87
88
89
90
91
92
93
@dataclass
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."
    persistent: ClassVar[bool] = True

Image dataclass

Bases: TraitBase

Image trait model.

Type trait model for image.

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@dataclass
class Image(TraitBase):
    """Image trait model.

    Type trait model for image.

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with version
    """

    name: ClassVar[str] = "Image"
    description: ClassVar[str] = "Image Trait"
    id: ClassVar[str] = "ayon.2d.Image.v1"
    persistent: ClassVar[bool] = True

IntendedUse dataclass

Bases: TraitBase

Intended use of the representation.

This trait describes the intended use of the representation. It can be used in cases where the other traits are not enough to describe the intended use. For example, a txt file with tracking points can be used as a corner pin in After Effect but not in Nuke.

Attributes:

Name Type Description
use str

Intended use description.

Source code in client/ayon_core/pipeline/traits/meta.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
@dataclass
class IntendedUse(TraitBase):
    """Intended use of the representation.

    This trait describes the intended use of the representation. It
    can be used in cases where the other traits are not enough to
    describe the intended use. For example, a txt file with tracking
    points can be used as a 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"
    persistent: ClassVar[bool] = True
    use: str

KeepOriginalLocation dataclass

Bases: TraitBase

Keep files in its original location.

Note

This is not a persistent trait.

Source code in client/ayon_core/pipeline/traits/meta.py
86
87
88
89
90
91
92
93
94
95
96
97
@dataclass
class KeepOriginalLocation(TraitBase):
    """Keep files in its original location.

    Note:
        This is not a persistent trait.

    """
    name: ClassVar[str] = "KeepOriginalLocation"
    description: ClassVar[str] = "Keep Original Location Trait Model"
    id: ClassVar[str] = "ayon.meta.KeepOriginalLocation.v1"
    persistent: ClassVar[bool] = False

Lighting dataclass

Bases: TraitBase

Lighting trait model.

Type trait for lighting data.

Sync with OpenAssetIO MediaCreation Traits.

Source code in client/ayon_core/pipeline/traits/three_dimensional.py
68
69
70
71
72
73
74
75
76
77
78
79
80
@dataclass
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."
    persistent: ClassVar[bool] = True

LocatableContent dataclass

Bases: 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 Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

location str

Location.

is_templated Optional[bool]

Is the location templated? Default is None.

Source code in client/ayon_core/pipeline/traits/content.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@dataclass
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 a 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"
    persistent: ClassVar[bool] = True
    location: str
    is_templated: Optional[bool] = None

MimeType dataclass

Bases: TraitBase

MimeType trait model.

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 Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

mime_type str

Mime type like image/jpeg.

Source code in client/ayon_core/pipeline/traits/content.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@dataclass
class MimeType(TraitBase):
    """MimeType trait model.

    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 a namespaced trait name with version
        mime_type (str): Mime type like image/jpeg.
    """

    name: ClassVar[str] = "MimeType"
    description: ClassVar[str] = "MimeType Trait Model"
    id: ClassVar[str] = "ayon.content.MimeType.v1"
    persistent: ClassVar[bool] = True
    mime_type: str

MissingTraitError

Bases: TypeError

Missing trait error exception.

This exception is raised when the trait is missing.

Source code in client/ayon_core/pipeline/traits/trait.py
143
144
145
146
147
class MissingTraitError(TypeError):
    """Missing trait error exception.

    This exception is raised when the trait is missing.
    """

Overscan dataclass

Bases: TraitBase

Overscan trait model.

This model represents an overscan (or underscan) trait. Defines the extra pixels around the image.

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with a version

left int

Left overscan/underscan.

right int

Right overscan/underscan.

top int

Top overscan/underscan.

bottom int

Bottom overscan/underscan.

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
@dataclass
class Overscan(TraitBase):
    """Overscan trait model.

    This model represents an overscan (or underscan) trait. Defines the
    extra pixels around the image.

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with a version
        left (int): Left overscan/underscan.
        right (int): Right overscan/underscan.
        top (int): Top overscan/underscan.
        bottom (int): Bottom overscan/underscan.
    """

    name: ClassVar[str] = "Overscan"
    description: ClassVar[str] = "Overscan Trait"
    id: ClassVar[str] = "ayon.2d.Overscan.v1"
    persistent: ClassVar[bool] = True
    left: int
    right: int
    top: int
    bottom: int

PGPSigned dataclass

Bases: DigitallySigned

PGP signed trait.

This trait holds PGP (RFC-4880) signed data.

Attributes:

Name Type Description
signed_data str

Signed data.

clear_text str

Clear text.

Source code in client/ayon_core/pipeline/traits/cryptography.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@dataclass
class PGPSigned(DigitallySigned):
    """PGP signed trait.

    This trait holds PGP (RFC-4880) signed data.

    Attributes:
        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."
    persistent: ClassVar[bool] = True
    signed_data: str
    clear_text: Optional[str] = None

Persistent dataclass

Bases: TraitBase

Persistent trait model.

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 Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with the version

Source code in client/ayon_core/pipeline/traits/lifecycle.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@dataclass
class Persistent(TraitBase):
    """Persistent trait model.

    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.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with the version
    """

    name: ClassVar[str] = "Persistent"
    description: ClassVar[str] = "Persistent Trait Model"
    id: ClassVar[str] = "ayon.lifecycle.Persistent.v1"
    # Note that this affects the 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.

        Args:
            representation (Representation): Representation model.

        Raises:
            TraitValidationError: If representation is marked
                as both Persistent and Transient.

        """
        if representation.contains_trait(Transient):
            msg = "Representation is marked as both Persistent and Transient."
            raise TraitValidationError(self.name, msg)

validate_trait(representation)

Validate representation is not Transient.

Parameters:

Name Type Description Default
representation Representation

Representation model.

required

Raises:

Type Description
TraitValidationError

If representation is marked as both Persistent and Transient.

Source code in client/ayon_core/pipeline/traits/lifecycle.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def validate_trait(self, representation) -> None:  # noqa: ANN001
    """Validate representation is not Transient.

    Args:
        representation (Representation): Representation model.

    Raises:
        TraitValidationError: If representation is marked
            as both Persistent and Transient.

    """
    if representation.contains_trait(Transient):
        msg = "Representation is marked as both Persistent and Transient."
        raise TraitValidationError(self.name, msg)

PixelBased dataclass

Bases: TraitBase

PixelBased trait model.

The pixel-related trait for image data.

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with a version

display_window_width int

Width of the image display window.

display_window_height int

Height of the image display window.

pixel_aspect_ratio float

Pixel aspect ratio.

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@dataclass
class PixelBased(TraitBase):
    """PixelBased trait model.

    The pixel-related trait for image data.

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with a version
        display_window_width (int): Width of the image display window.
        display_window_height (int): Height of the image display window.
        pixel_aspect_ratio (float): Pixel aspect ratio.
    """

    name: ClassVar[str] = "PixelBased"
    description: ClassVar[str] = "PixelBased Trait Model"
    id: ClassVar[str] = "ayon.2d.PixelBased.v1"
    persistent: ClassVar[bool] = True
    display_window_width: int
    display_window_height: int
    pixel_aspect_ratio: float

Planar dataclass

Bases: TraitBase

Planar trait model.

This model represents an Image with planar configuration.

Todo
  • (antirotor): Is this really a planar configuration? As with bit planes and everything? If it serves as differentiator for Deep images, should it be named differently? Like Raster?

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be namespaced trait name with version

planar_configuration str

Planar configuration.

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@dataclass
class Planar(TraitBase):
    """Planar trait model.

    This model represents an Image with planar configuration.

    Todo:
        * (antirotor): Is this really a planar configuration? As with
            bit planes and everything? If it serves as differentiator for
            Deep images, should it be named differently? Like Raster?

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be namespaced trait name with version
        planar_configuration (str): Planar configuration.
    """

    name: ClassVar[str] = "Planar"
    description: ClassVar[str] = "Planar Trait Model"
    id: ClassVar[str] = "ayon.2d.Planar.v1"
    persistent: ClassVar[bool] = True
    planar_configuration: str

Representation

Bases: Generic[T]

Representation of products.

Representation defines a collection of individual properties that describe the specific "form" of the product. A trait represents a set of properties therefore, the Representation is a collection of traits.

It holds methods to add, remove, get, and check for the existence of a trait in the representation.

Note

PLR0904 is the rule for checking the number of public methods in a class.

Parameters:

Name Type Description Default
name str

Representation name. Must be unique within instance.

required
representation_id str

Representation ID.

None
Source code in client/ayon_core/pipeline/traits/representation.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
class Representation(Generic[T]):  # noqa: PLR0904
    """Representation of products.

    Representation defines a collection of individual properties that describe
    the specific "form" of the product. A trait represents a set of
    properties therefore, the Representation is a collection of traits.

    It holds methods to add, remove, get, and check for the existence of a
    trait in the representation.

    Note:
        `PLR0904` is the rule for checking the number of public methods
        in a class.

    Arguments:
        name (str): Representation name. Must be unique within instance.
        representation_id (str): Representation ID.
    """

    _data: dict[str, T]
    _module_blacklist: ClassVar[list[str]] = [
        "_", "builtins", "pydantic",
    ]
    name: str
    representation_id: str

    def __hash__(self):
        """Return hash of the representation ID."""
        return hash(self.representation_id)

    def __getitem__(self, key: str) -> T:
        """Get the trait by ID.

        Args:
            key (str): Trait ID.

        Returns:
            TraitBase: Trait instance.

        """
        return self.get_trait_by_id(key)

    def __setitem__(self, key: str, value: T) -> None:
        """Set the trait by ID.

        Args:
            key (str): Trait ID.
            value (TraitBase): Trait instance.

        """
        with contextlib.suppress(KeyError):
            self._data.pop(key)

        self.add_trait(value)

    def __delitem__(self, key: str) -> None:
        """Remove the trait by ID.

        Args:
            key (str): Trait ID.


        """
        self.remove_trait_by_id(key)

    def __contains__(self, key: str) -> bool:
        """Check if the trait exists by ID.

        Args:
            key (str): Trait ID.

        Returns:
            bool: True if the trait exists, False otherwise.

        """
        return self.contains_trait_by_id(key)

    def __iter__(self):
        """Return the trait ID iterator."""
        return iter(self._data)

    def __str__(self):
        """Return the representation name."""
        return self.name

    def items(self) -> ItemsView[str, T]:
        """Return the traits as items."""
        return ItemsView(self._data)

    def add_trait(self, trait: T, *, exists_ok: bool = False) -> None:
        """Add a trait to the Representation.

        Args:
            trait (TraitBase): Trait to add.
            exists_ok (bool, optional): If True, do not raise an error if the
                trait already exists. Defaults to False.

        Raises:
            ValueError: If the trait ID is not provided, or the trait already
                exists.

        """
        if not hasattr(trait, "id"):
            error_msg = f"Invalid trait {trait} - ID is required."
            raise ValueError(error_msg)
        if trait.id in self._data and not exists_ok:
            error_msg = f"Trait with ID {trait.id} already exists."
            raise ValueError(error_msg)
        self._data[trait.id] = trait

    def add_traits(
            self, traits: list[T], *, exists_ok: bool = False) -> None:
        """Add a list of traits to the Representation.

        Args:
            traits (list[TraitBase]): List of traits to add.
            exists_ok (bool, optional): If True, do not raise an error if the
                trait already exists. Defaults to False.

        """
        for trait in traits:
            self.add_trait(trait, exists_ok=exists_ok)

    def remove_trait(self, trait: Type[TraitBase]) -> None:
        """Remove a trait from the data.

        Args:
            trait (TraitBase, optional): Trait class.

        Raises:
            ValueError: If the trait is not found.

        """
        try:
            self._data.pop(str(trait.id))
        except KeyError as e:
            error_msg = f"Trait with ID {trait.id} not found."
            raise ValueError(error_msg) from e

    def remove_trait_by_id(self, trait_id: str) -> None:
        """Remove a trait from the data by its ID.

        Args:
            trait_id (str): Trait ID.

        Raises:
            ValueError: If the trait is not found.

        """
        try:
            self._data.pop(trait_id)
        except KeyError as e:
            error_msg = f"Trait with ID {trait_id} not found."
            raise ValueError(error_msg) from e

    def remove_traits(self, traits: list[Type[T]]) -> None:
        """Remove a list of traits from the Representation.

        If no trait IDs or traits are provided, all traits will be removed.

        Args:
            traits (list[TraitBase]): List of trait classes.

        """
        if not traits:
            self._data = {}
            return

        for trait in traits:
            self.remove_trait(trait)

    def remove_traits_by_id(self, trait_ids: list[str]) -> None:
        """Remove a list of traits from the Representation by their ID.

        If no trait IDs or traits are provided, all traits will be removed.

        Args:
            trait_ids (list[str], optional): List of trait IDs.

        """
        for trait_id in trait_ids:
            self.remove_trait_by_id(trait_id)

    def has_traits(self) -> bool:
        """Check if the Representation has any traits.

        Returns:
            bool: True if the Representation has any traits, False otherwise.

        """
        return bool(self._data)

    def contains_trait(self, trait: Type[T]) -> bool:
        """Check if the trait exists in the Representation.

        Args:
            trait (TraitBase): Trait class.

        Returns:
            bool: True if the trait exists, False otherwise.

        """
        return bool(self._data.get(str(trait.id)))

    def contains_trait_by_id(self, trait_id: str) -> bool:
        """Check if the trait exists using trait id.

        Args:
            trait_id (str): Trait ID.

        Returns:
            bool: True if the trait exists, False otherwise.

        """
        return bool(self._data.get(trait_id))

    def contains_traits(self, traits: list[Type[T]]) -> bool:
        """Check if the traits exist.

        Args:
            traits (list[TraitBase], optional): List of trait classes.

        Returns:
            bool: True if all traits exist, False otherwise.

        """
        return all(self.contains_trait(trait=trait) for trait in traits)

    def contains_traits_by_id(self, trait_ids: list[str]) -> bool:
        """Check if the traits exist by id.

        If no trait IDs or traits are provided, it will check if the
        representation has any traits.

        Args:
            trait_ids (list[str]): List of trait IDs.

        Returns:
            bool: True if all traits exist, False otherwise.

        """
        return all(
            self.contains_trait_by_id(trait_id) for trait_id in trait_ids
        )

    def get_trait(self, trait: Type[T]) -> T:
        """Get a trait from the representation.

        Args:
            trait (TraitBase, optional): Trait class.

        Returns:
            TraitBase: Trait instance.

        Raises:
            MissingTraitError: If the trait is not found.

        """
        try:
            return self._data[str(trait.id)]
        except KeyError as e:
            msg = f"Trait with ID {trait.id} not found."
            raise MissingTraitError(msg) from e

    def get_trait_by_id(self, trait_id: str) -> T:
        # sourcery skip: use-named-expression
        """Get a trait from the representation by id.

        Args:
            trait_id (str): Trait ID.

        Returns:
            TraitBase: Trait instance.

        Raises:
            MissingTraitError: If the trait is not found.

        """
        version = _get_version_from_id(trait_id)
        if version:
            try:
                return self._data[trait_id]
            except KeyError as e:
                msg = f"Trait with ID {trait_id} not found."
                raise MissingTraitError(msg) from e

        result = next(
            (
                self._data.get(trait_id)
                for trait_id in self._data
                if trait_id.startswith(trait_id)
            ),
            None,
        )
        if result is None:
            msg = f"Trait with ID {trait_id} not found."
            raise MissingTraitError(msg)
        return result

    def get_traits(self,
                     traits: Optional[list[Type[T]]] = None
     ) -> dict[str, T]:
        """Get a list of traits from the representation.

        If no trait IDs or traits are provided, all traits will be returned.

        Args:
            traits (list[TraitBase], optional): List of trait classes.

        Returns:
            dict: Dictionary of traits.

        """
        result: dict[str, T] = {}
        if not traits:
            for trait_id in self._data:
                result[trait_id] = self.get_trait_by_id(trait_id=trait_id)
            return result

        for trait in traits:
            result[str(trait.id)] = self.get_trait(trait=trait)
        return result

    def get_traits_by_ids(self, trait_ids: list[str]) -> dict[str, T]:
        """Get a list of traits from the representation by their id.

        If no trait IDs or traits are provided, all traits will be returned.

        Args:
            trait_ids (list[str]): List of trait IDs.

        Returns:
            dict: Dictionary of traits.

        """
        return {
            trait_id: self.get_trait_by_id(trait_id)
            for trait_id in trait_ids
        }

    def traits_as_dict(self) -> dict:
        """Return the traits from Representation data as a dictionary.

        Returns:
            dict: Traits data dictionary.

        """
        return {
            trait_id: trait.as_dict()
            for trait_id, trait in self._data.items()
            if trait and trait_id
        }

    def __len__(self):
        """Return the length of the data."""
        return len(self._data)

    def __init__(
            self,
            name: str,
            representation_id: Optional[str] = None,
            traits: Optional[list[T]] = None):
        """Initialize the data.

        Args:
            name (str): Representation name. Must be unique within instance.
            representation_id (str, optional): Representation ID.
            traits (list[TraitBase], optional): List of traits.

        """
        self.name = name
        self.representation_id = representation_id or uuid.uuid4().hex
        self._data = {}
        if traits:
            for trait in traits:
                self.add_trait(trait)

    @staticmethod
    def _get_version_from_id(trait_id: str) -> Union[int, None]:
        # sourcery skip: use-named-expression
        """Check if the trait has a version specified.

        Args:
            trait_id (str): Trait ID.

        Returns:
            int: Trait version.
            None: If the trait id does not have a version.

        """
        version_regex = r"v(\d+)$"
        match = re.search(version_regex, trait_id)
        return int(match[1]) if match else None

    def __eq__(self, other: object) -> bool:  # noqa: PLR0911
        """Check if the representation is equal to another.

        Args:
            other (Representation): Representation to compare.

        Returns:
            bool: True if the representations are equal, False otherwise.

        """
        if not isinstance(other, Representation):
            return False

        if self.representation_id != other.representation_id:
            return False

        if self.name != other.name:
            return False

        # number of traits
        if len(self) != len(other):
            return False

        for trait_id, trait in self._data.items():
            if trait_id not in other._data:
                return False
            if trait != other._data[trait_id]:
                return False

        return True

    @classmethod
    @lru_cache(maxsize=64)
    def _get_possible_trait_classes_from_modules(
            cls,
            trait_id: str) -> set[type[T]]:
        """Get possible trait classes from modules.

        Args:
            trait_id (str): Trait ID.

        Returns:
            set[type[T]]: Set of trait classes.

        """
        modules = sys.modules.copy()
        filtered_modules = modules.copy()
        for module_name in modules:
            for bl_module in cls._module_blacklist:
                if module_name.startswith(bl_module):
                    filtered_modules.pop(module_name)

        trait_candidates = set()
        for module in filtered_modules.values():
            if not module:
                continue

            for attr_name in dir(module):
                klass = getattr(module, attr_name)
                if not inspect.isclass(klass):
                    continue
                # This needs to be done because of the bug? In
                # python ABCMeta, where ``issubclass`` is not working
                # if it hits the GenericAlias (that is in fact
                # tuple[int, int]). This is added to the scope by
                # the ``types`` module.
                if type(klass) is GenericAlias:
                    continue
                if issubclass(klass, TraitBase) \
                        and str(klass.id).startswith(trait_id):
                    trait_candidates.add(klass)
        # I
        return trait_candidates  # type: ignore[return-value]

    @classmethod
    @lru_cache(maxsize=64)
    def _get_trait_class(
            cls, trait_id: str) -> Union[Type[T], None]:
        """Get the trait class with corresponding to given ID.

        This method will search for the trait class in all the modules except
        the blocklisted modules. There is some issue in Pydantic where
        ``issubclass`` is not working properly, so we are excluding explicit
        modules with offending classes. This list can be updated as needed to
        speed up the search.

        Args:
            trait_id (str): Trait ID.

        Returns:
            Type[TraitBase]: Trait class.

        """
        version = cls._get_version_from_id(trait_id)

        trait_candidates = cls._get_possible_trait_classes_from_modules(
            trait_id
        )
        if not trait_candidates:
            return None

        for trait_class in trait_candidates:
            if trait_class.id == trait_id:
                # we found a direct match
                return trait_class

        # if we didn't find direct match, we will search for the highest
        # version of the trait.
        if not version:
            # sourcery skip: use-named-expression
            trait_versions = [
                trait_class for trait_class in trait_candidates
                if re.match(
                    rf"{trait_id}.v(\d+)$", str(trait_class.id))
            ]
            if trait_versions:
                def _get_version_by_id(trait_klass: Type[T]) -> int:
                    match = re.search(r"v(\d+)$", str(trait_klass.id))
                    return int(match[1]) if match else 0

                error: LooseMatchingTraitError = LooseMatchingTraitError(
                    "Found trait that might match.")
                error.found_trait = max(
                    trait_versions, key=_get_version_by_id)
                error.expected_id = trait_id
                raise error

        return None

    @classmethod
    def get_trait_class_by_trait_id(cls, trait_id: str) -> Type[T]:
        """Get the trait class for the given trait ID.

        Args:
            trait_id (str): Trait ID.

        Returns:
            type[TraitBase]: Trait class.

        Raises:
            IncompatibleTraitVersionError: If the trait version is incompatible
                with the current version of the trait.

        """
        try:
            trait_class = cls._get_trait_class(trait_id=trait_id)
        except LooseMatchingTraitError as e:
            requested_version = _get_version_from_id(trait_id)
            found_version = _get_version_from_id(e.found_trait.id)
            if found_version is None and not requested_version:
                msg = (
                    "Trait found with no version and requested version "
                    "is not specified."
                )
                raise IncompatibleTraitVersionError(msg) from e

            if found_version is None:
                msg = (
                    f"Trait {e.found_trait.id} found with no version, "
                    "but requested version is specified."
                )
                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 "
                    f"higher than the found trait version {found_version}."
                )
                raise IncompatibleTraitVersionError(error_msg) from e

            if requested_version < found_version and hasattr(
                    e.found_trait, "upgrade"):
                error_msg = (
                    "Requested trait version "
                    f"{requested_version} is lower "
                    f"than the found trait version {found_version}."
                )
                error: UpgradableTraitError = UpgradableTraitError(error_msg)
                error.trait = e.found_trait
                raise error from e
        return trait_class  # type: ignore[return-value]

    @classmethod
    def from_dict(
            cls: Type[Representation],
            name: str,
            representation_id: Optional[str] = None,
            trait_data: Optional[dict] = None) -> Representation:
        """Create a representation from a dictionary.

        Args:
            name (str): Representation name.
            representation_id (str, optional): Representation ID.
            trait_data (dict): Representation data. Dictionary with keys
                as trait ids and values as trait data. Example::

                    {
                        "ayon.2d.PixelBased.v1": {
                            "display_window_width": 1920,
                            "display_window_height": 1080
                        },
                        "ayon.2d.Planar.v1": {
                            "channels": 3
                        }
                    }

        Returns:
            Representation: Representation instance.

        Raises:
            ValueError: If the trait model with ID is not found.
            TypeError: If the trait data is not a dictionary.
            IncompatibleTraitVersionError: If the trait version is incompatible

        """
        if not trait_data:
            trait_data = {}
        traits = []
        for trait_id, value in trait_data.items():
            if not isinstance(value, dict):
                msg = (
                    f"Invalid trait data for trait ID {trait_id}. "
                    "Trait data must be a dictionary."
                )
                raise TypeError(msg)

            try:
                trait_class = cls.get_trait_class_by_trait_id(trait_id)
            except UpgradableTraitError as e:
                # we found a newer version of trait, we will upgrade the data
                if hasattr(e.trait, "upgrade"):
                    traits.append(e.trait.upgrade(value))
                else:
                    msg = (
                        f"Newer version of trait {e.trait.id} found "
                        f"for requested {trait_id} but without "
                        "upgrade method."
                    )
                    raise IncompatibleTraitVersionError(msg) from e
            else:
                if not trait_class:
                    error_msg = f"Trait model with ID {trait_id} not found."
                    raise ValueError(error_msg)

                traits.append(trait_class(**value))

        return cls(
            name=name, representation_id=representation_id, traits=traits)

    def validate(self) -> None:
        """Validate the representation.

        This method will validate all the traits in the representation.

        Raises:
            TraitValidationError: If the trait is invalid within representation

        """
        errors = []
        for trait in self._data.values():
            # we do this in the loop to catch all the errors
            try:
                trait.validate_trait(self)
            except TraitValidationError as e:  # noqa: PERF203
                errors.append(str(e))
        if errors:
            msg = "\n".join(errors)
            scope = self.name
            raise TraitValidationError(scope, msg)

__contains__(key)

Check if the trait exists by ID.

Parameters:

Name Type Description Default
key str

Trait ID.

required

Returns:

Name Type Description
bool bool

True if the trait exists, False otherwise.

Source code in client/ayon_core/pipeline/traits/representation.py
112
113
114
115
116
117
118
119
120
121
122
def __contains__(self, key: str) -> bool:
    """Check if the trait exists by ID.

    Args:
        key (str): Trait ID.

    Returns:
        bool: True if the trait exists, False otherwise.

    """
    return self.contains_trait_by_id(key)

__delitem__(key)

Remove the trait by ID.

Parameters:

Name Type Description Default
key str

Trait ID.

required
Source code in client/ayon_core/pipeline/traits/representation.py
102
103
104
105
106
107
108
109
110
def __delitem__(self, key: str) -> None:
    """Remove the trait by ID.

    Args:
        key (str): Trait ID.


    """
    self.remove_trait_by_id(key)

__eq__(other)

Check if the representation is equal to another.

Parameters:

Name Type Description Default
other Representation

Representation to compare.

required

Returns:

Name Type Description
bool bool

True if the representations are equal, False otherwise.

Source code in client/ayon_core/pipeline/traits/representation.py
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
def __eq__(self, other: object) -> bool:  # noqa: PLR0911
    """Check if the representation is equal to another.

    Args:
        other (Representation): Representation to compare.

    Returns:
        bool: True if the representations are equal, False otherwise.

    """
    if not isinstance(other, Representation):
        return False

    if self.representation_id != other.representation_id:
        return False

    if self.name != other.name:
        return False

    # number of traits
    if len(self) != len(other):
        return False

    for trait_id, trait in self._data.items():
        if trait_id not in other._data:
            return False
        if trait != other._data[trait_id]:
            return False

    return True

__getitem__(key)

Get the trait by ID.

Parameters:

Name Type Description Default
key str

Trait ID.

required

Returns:

Name Type Description
TraitBase T

Trait instance.

Source code in client/ayon_core/pipeline/traits/representation.py
77
78
79
80
81
82
83
84
85
86
87
def __getitem__(self, key: str) -> T:
    """Get the trait by ID.

    Args:
        key (str): Trait ID.

    Returns:
        TraitBase: Trait instance.

    """
    return self.get_trait_by_id(key)

__hash__()

Return hash of the representation ID.

Source code in client/ayon_core/pipeline/traits/representation.py
73
74
75
def __hash__(self):
    """Return hash of the representation ID."""
    return hash(self.representation_id)

__init__(name, representation_id=None, traits=None)

Initialize the data.

Parameters:

Name Type Description Default
name str

Representation name. Must be unique within instance.

required
representation_id str

Representation ID.

None
traits list[TraitBase]

List of traits.

None
Source code in client/ayon_core/pipeline/traits/representation.py
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
def __init__(
        self,
        name: str,
        representation_id: Optional[str] = None,
        traits: Optional[list[T]] = None):
    """Initialize the data.

    Args:
        name (str): Representation name. Must be unique within instance.
        representation_id (str, optional): Representation ID.
        traits (list[TraitBase], optional): List of traits.

    """
    self.name = name
    self.representation_id = representation_id or uuid.uuid4().hex
    self._data = {}
    if traits:
        for trait in traits:
            self.add_trait(trait)

__iter__()

Return the trait ID iterator.

Source code in client/ayon_core/pipeline/traits/representation.py
124
125
126
def __iter__(self):
    """Return the trait ID iterator."""
    return iter(self._data)

__len__()

Return the length of the data.

Source code in client/ayon_core/pipeline/traits/representation.py
400
401
402
def __len__(self):
    """Return the length of the data."""
    return len(self._data)

__setitem__(key, value)

Set the trait by ID.

Parameters:

Name Type Description Default
key str

Trait ID.

required
value TraitBase

Trait instance.

required
Source code in client/ayon_core/pipeline/traits/representation.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def __setitem__(self, key: str, value: T) -> None:
    """Set the trait by ID.

    Args:
        key (str): Trait ID.
        value (TraitBase): Trait instance.

    """
    with contextlib.suppress(KeyError):
        self._data.pop(key)

    self.add_trait(value)

__str__()

Return the representation name.

Source code in client/ayon_core/pipeline/traits/representation.py
128
129
130
def __str__(self):
    """Return the representation name."""
    return self.name

add_trait(trait, *, exists_ok=False)

Add a trait to the Representation.

Parameters:

Name Type Description Default
trait TraitBase

Trait to add.

required
exists_ok bool

If True, do not raise an error if the trait already exists. Defaults to False.

False

Raises:

Type Description
ValueError

If the trait ID is not provided, or the trait already exists.

Source code in client/ayon_core/pipeline/traits/representation.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def add_trait(self, trait: T, *, exists_ok: bool = False) -> None:
    """Add a trait to the Representation.

    Args:
        trait (TraitBase): Trait to add.
        exists_ok (bool, optional): If True, do not raise an error if the
            trait already exists. Defaults to False.

    Raises:
        ValueError: If the trait ID is not provided, or the trait already
            exists.

    """
    if not hasattr(trait, "id"):
        error_msg = f"Invalid trait {trait} - ID is required."
        raise ValueError(error_msg)
    if trait.id in self._data and not exists_ok:
        error_msg = f"Trait with ID {trait.id} already exists."
        raise ValueError(error_msg)
    self._data[trait.id] = trait

add_traits(traits, *, exists_ok=False)

Add a list of traits to the Representation.

Parameters:

Name Type Description Default
traits list[TraitBase]

List of traits to add.

required
exists_ok bool

If True, do not raise an error if the trait already exists. Defaults to False.

False
Source code in client/ayon_core/pipeline/traits/representation.py
157
158
159
160
161
162
163
164
165
166
167
168
def add_traits(
        self, traits: list[T], *, exists_ok: bool = False) -> None:
    """Add a list of traits to the Representation.

    Args:
        traits (list[TraitBase]): List of traits to add.
        exists_ok (bool, optional): If True, do not raise an error if the
            trait already exists. Defaults to False.

    """
    for trait in traits:
        self.add_trait(trait, exists_ok=exists_ok)

contains_trait(trait)

Check if the trait exists in the Representation.

Parameters:

Name Type Description Default
trait TraitBase

Trait class.

required

Returns:

Name Type Description
bool bool

True if the trait exists, False otherwise.

Source code in client/ayon_core/pipeline/traits/representation.py
239
240
241
242
243
244
245
246
247
248
249
def contains_trait(self, trait: Type[T]) -> bool:
    """Check if the trait exists in the Representation.

    Args:
        trait (TraitBase): Trait class.

    Returns:
        bool: True if the trait exists, False otherwise.

    """
    return bool(self._data.get(str(trait.id)))

contains_trait_by_id(trait_id)

Check if the trait exists using trait id.

Parameters:

Name Type Description Default
trait_id str

Trait ID.

required

Returns:

Name Type Description
bool bool

True if the trait exists, False otherwise.

Source code in client/ayon_core/pipeline/traits/representation.py
251
252
253
254
255
256
257
258
259
260
261
def contains_trait_by_id(self, trait_id: str) -> bool:
    """Check if the trait exists using trait id.

    Args:
        trait_id (str): Trait ID.

    Returns:
        bool: True if the trait exists, False otherwise.

    """
    return bool(self._data.get(trait_id))

contains_traits(traits)

Check if the traits exist.

Parameters:

Name Type Description Default
traits list[TraitBase]

List of trait classes.

required

Returns:

Name Type Description
bool bool

True if all traits exist, False otherwise.

Source code in client/ayon_core/pipeline/traits/representation.py
263
264
265
266
267
268
269
270
271
272
273
def contains_traits(self, traits: list[Type[T]]) -> bool:
    """Check if the traits exist.

    Args:
        traits (list[TraitBase], optional): List of trait classes.

    Returns:
        bool: True if all traits exist, False otherwise.

    """
    return all(self.contains_trait(trait=trait) for trait in traits)

contains_traits_by_id(trait_ids)

Check if the traits exist by id.

If no trait IDs or traits are provided, it will check if the representation has any traits.

Parameters:

Name Type Description Default
trait_ids list[str]

List of trait IDs.

required

Returns:

Name Type Description
bool bool

True if all traits exist, False otherwise.

Source code in client/ayon_core/pipeline/traits/representation.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
def contains_traits_by_id(self, trait_ids: list[str]) -> bool:
    """Check if the traits exist by id.

    If no trait IDs or traits are provided, it will check if the
    representation has any traits.

    Args:
        trait_ids (list[str]): List of trait IDs.

    Returns:
        bool: True if all traits exist, False otherwise.

    """
    return all(
        self.contains_trait_by_id(trait_id) for trait_id in trait_ids
    )

from_dict(name, representation_id=None, trait_data=None) classmethod

Create a representation from a dictionary.

Parameters:

Name Type Description Default
name str

Representation name.

required
representation_id str

Representation ID.

None
trait_data dict

Representation data. Dictionary with keys as trait ids and values as trait data. Example::

{
    "ayon.2d.PixelBased.v1": {
        "display_window_width": 1920,
        "display_window_height": 1080
    },
    "ayon.2d.Planar.v1": {
        "channels": 3
    }
}
None

Returns:

Name Type Description
Representation Representation

Representation instance.

Raises:

Type Description
ValueError

If the trait model with ID is not found.

TypeError

If the trait data is not a dictionary.

IncompatibleTraitVersionError

If the trait version is incompatible

Source code in client/ayon_core/pipeline/traits/representation.py
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
@classmethod
def from_dict(
        cls: Type[Representation],
        name: str,
        representation_id: Optional[str] = None,
        trait_data: Optional[dict] = None) -> Representation:
    """Create a representation from a dictionary.

    Args:
        name (str): Representation name.
        representation_id (str, optional): Representation ID.
        trait_data (dict): Representation data. Dictionary with keys
            as trait ids and values as trait data. Example::

                {
                    "ayon.2d.PixelBased.v1": {
                        "display_window_width": 1920,
                        "display_window_height": 1080
                    },
                    "ayon.2d.Planar.v1": {
                        "channels": 3
                    }
                }

    Returns:
        Representation: Representation instance.

    Raises:
        ValueError: If the trait model with ID is not found.
        TypeError: If the trait data is not a dictionary.
        IncompatibleTraitVersionError: If the trait version is incompatible

    """
    if not trait_data:
        trait_data = {}
    traits = []
    for trait_id, value in trait_data.items():
        if not isinstance(value, dict):
            msg = (
                f"Invalid trait data for trait ID {trait_id}. "
                "Trait data must be a dictionary."
            )
            raise TypeError(msg)

        try:
            trait_class = cls.get_trait_class_by_trait_id(trait_id)
        except UpgradableTraitError as e:
            # we found a newer version of trait, we will upgrade the data
            if hasattr(e.trait, "upgrade"):
                traits.append(e.trait.upgrade(value))
            else:
                msg = (
                    f"Newer version of trait {e.trait.id} found "
                    f"for requested {trait_id} but without "
                    "upgrade method."
                )
                raise IncompatibleTraitVersionError(msg) from e
        else:
            if not trait_class:
                error_msg = f"Trait model with ID {trait_id} not found."
                raise ValueError(error_msg)

            traits.append(trait_class(**value))

    return cls(
        name=name, representation_id=representation_id, traits=traits)

get_trait(trait)

Get a trait from the representation.

Parameters:

Name Type Description Default
trait TraitBase

Trait class.

required

Returns:

Name Type Description
TraitBase T

Trait instance.

Raises:

Type Description
MissingTraitError

If the trait is not found.

Source code in client/ayon_core/pipeline/traits/representation.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
def get_trait(self, trait: Type[T]) -> T:
    """Get a trait from the representation.

    Args:
        trait (TraitBase, optional): Trait class.

    Returns:
        TraitBase: Trait instance.

    Raises:
        MissingTraitError: If the trait is not found.

    """
    try:
        return self._data[str(trait.id)]
    except KeyError as e:
        msg = f"Trait with ID {trait.id} not found."
        raise MissingTraitError(msg) from e

get_trait_by_id(trait_id)

Get a trait from the representation by id.

Parameters:

Name Type Description Default
trait_id str

Trait ID.

required

Returns:

Name Type Description
TraitBase T

Trait instance.

Raises:

Type Description
MissingTraitError

If the trait is not found.

Source code in client/ayon_core/pipeline/traits/representation.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
def get_trait_by_id(self, trait_id: str) -> T:
    # sourcery skip: use-named-expression
    """Get a trait from the representation by id.

    Args:
        trait_id (str): Trait ID.

    Returns:
        TraitBase: Trait instance.

    Raises:
        MissingTraitError: If the trait is not found.

    """
    version = _get_version_from_id(trait_id)
    if version:
        try:
            return self._data[trait_id]
        except KeyError as e:
            msg = f"Trait with ID {trait_id} not found."
            raise MissingTraitError(msg) from e

    result = next(
        (
            self._data.get(trait_id)
            for trait_id in self._data
            if trait_id.startswith(trait_id)
        ),
        None,
    )
    if result is None:
        msg = f"Trait with ID {trait_id} not found."
        raise MissingTraitError(msg)
    return result

get_trait_class_by_trait_id(trait_id) classmethod

Get the trait class for the given trait ID.

Parameters:

Name Type Description Default
trait_id str

Trait ID.

required

Returns:

Type Description
Type[T]

type[TraitBase]: Trait class.

Raises:

Type Description
IncompatibleTraitVersionError

If the trait version is incompatible with the current version of the trait.

Source code in client/ayon_core/pipeline/traits/representation.py
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
@classmethod
def get_trait_class_by_trait_id(cls, trait_id: str) -> Type[T]:
    """Get the trait class for the given trait ID.

    Args:
        trait_id (str): Trait ID.

    Returns:
        type[TraitBase]: Trait class.

    Raises:
        IncompatibleTraitVersionError: If the trait version is incompatible
            with the current version of the trait.

    """
    try:
        trait_class = cls._get_trait_class(trait_id=trait_id)
    except LooseMatchingTraitError as e:
        requested_version = _get_version_from_id(trait_id)
        found_version = _get_version_from_id(e.found_trait.id)
        if found_version is None and not requested_version:
            msg = (
                "Trait found with no version and requested version "
                "is not specified."
            )
            raise IncompatibleTraitVersionError(msg) from e

        if found_version is None:
            msg = (
                f"Trait {e.found_trait.id} found with no version, "
                "but requested version is specified."
            )
            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 "
                f"higher than the found trait version {found_version}."
            )
            raise IncompatibleTraitVersionError(error_msg) from e

        if requested_version < found_version and hasattr(
                e.found_trait, "upgrade"):
            error_msg = (
                "Requested trait version "
                f"{requested_version} is lower "
                f"than the found trait version {found_version}."
            )
            error: UpgradableTraitError = UpgradableTraitError(error_msg)
            error.trait = e.found_trait
            raise error from e
    return trait_class  # type: ignore[return-value]

get_traits(traits=None)

Get a list of traits from the representation.

If no trait IDs or traits are provided, all traits will be returned.

Parameters:

Name Type Description Default
traits list[TraitBase]

List of trait classes.

None

Returns:

Name Type Description
dict dict[str, T]

Dictionary of traits.

Source code in client/ayon_core/pipeline/traits/representation.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def get_traits(self,
                 traits: Optional[list[Type[T]]] = None
 ) -> dict[str, T]:
    """Get a list of traits from the representation.

    If no trait IDs or traits are provided, all traits will be returned.

    Args:
        traits (list[TraitBase], optional): List of trait classes.

    Returns:
        dict: Dictionary of traits.

    """
    result: dict[str, T] = {}
    if not traits:
        for trait_id in self._data:
            result[trait_id] = self.get_trait_by_id(trait_id=trait_id)
        return result

    for trait in traits:
        result[str(trait.id)] = self.get_trait(trait=trait)
    return result

get_traits_by_ids(trait_ids)

Get a list of traits from the representation by their id.

If no trait IDs or traits are provided, all traits will be returned.

Parameters:

Name Type Description Default
trait_ids list[str]

List of trait IDs.

required

Returns:

Name Type Description
dict dict[str, T]

Dictionary of traits.

Source code in client/ayon_core/pipeline/traits/representation.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
def get_traits_by_ids(self, trait_ids: list[str]) -> dict[str, T]:
    """Get a list of traits from the representation by their id.

    If no trait IDs or traits are provided, all traits will be returned.

    Args:
        trait_ids (list[str]): List of trait IDs.

    Returns:
        dict: Dictionary of traits.

    """
    return {
        trait_id: self.get_trait_by_id(trait_id)
        for trait_id in trait_ids
    }

has_traits()

Check if the Representation has any traits.

Returns:

Name Type Description
bool bool

True if the Representation has any traits, False otherwise.

Source code in client/ayon_core/pipeline/traits/representation.py
230
231
232
233
234
235
236
237
def has_traits(self) -> bool:
    """Check if the Representation has any traits.

    Returns:
        bool: True if the Representation has any traits, False otherwise.

    """
    return bool(self._data)

items()

Return the traits as items.

Source code in client/ayon_core/pipeline/traits/representation.py
132
133
134
def items(self) -> ItemsView[str, T]:
    """Return the traits as items."""
    return ItemsView(self._data)

remove_trait(trait)

Remove a trait from the data.

Parameters:

Name Type Description Default
trait TraitBase

Trait class.

required

Raises:

Type Description
ValueError

If the trait is not found.

Source code in client/ayon_core/pipeline/traits/representation.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def remove_trait(self, trait: Type[TraitBase]) -> None:
    """Remove a trait from the data.

    Args:
        trait (TraitBase, optional): Trait class.

    Raises:
        ValueError: If the trait is not found.

    """
    try:
        self._data.pop(str(trait.id))
    except KeyError as e:
        error_msg = f"Trait with ID {trait.id} not found."
        raise ValueError(error_msg) from e

remove_trait_by_id(trait_id)

Remove a trait from the data by its ID.

Parameters:

Name Type Description Default
trait_id str

Trait ID.

required

Raises:

Type Description
ValueError

If the trait is not found.

Source code in client/ayon_core/pipeline/traits/representation.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def remove_trait_by_id(self, trait_id: str) -> None:
    """Remove a trait from the data by its ID.

    Args:
        trait_id (str): Trait ID.

    Raises:
        ValueError: If the trait is not found.

    """
    try:
        self._data.pop(trait_id)
    except KeyError as e:
        error_msg = f"Trait with ID {trait_id} not found."
        raise ValueError(error_msg) from e

remove_traits(traits)

Remove a list of traits from the Representation.

If no trait IDs or traits are provided, all traits will be removed.

Parameters:

Name Type Description Default
traits list[TraitBase]

List of trait classes.

required
Source code in client/ayon_core/pipeline/traits/representation.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
def remove_traits(self, traits: list[Type[T]]) -> None:
    """Remove a list of traits from the Representation.

    If no trait IDs or traits are provided, all traits will be removed.

    Args:
        traits (list[TraitBase]): List of trait classes.

    """
    if not traits:
        self._data = {}
        return

    for trait in traits:
        self.remove_trait(trait)

remove_traits_by_id(trait_ids)

Remove a list of traits from the Representation by their ID.

If no trait IDs or traits are provided, all traits will be removed.

Parameters:

Name Type Description Default
trait_ids list[str]

List of trait IDs.

required
Source code in client/ayon_core/pipeline/traits/representation.py
218
219
220
221
222
223
224
225
226
227
228
def remove_traits_by_id(self, trait_ids: list[str]) -> None:
    """Remove a list of traits from the Representation by their ID.

    If no trait IDs or traits are provided, all traits will be removed.

    Args:
        trait_ids (list[str], optional): List of trait IDs.

    """
    for trait_id in trait_ids:
        self.remove_trait_by_id(trait_id)

traits_as_dict()

Return the traits from Representation data as a dictionary.

Returns:

Name Type Description
dict dict

Traits data dictionary.

Source code in client/ayon_core/pipeline/traits/representation.py
387
388
389
390
391
392
393
394
395
396
397
398
def traits_as_dict(self) -> dict:
    """Return the traits from Representation data as a dictionary.

    Returns:
        dict: Traits data dictionary.

    """
    return {
        trait_id: trait.as_dict()
        for trait_id, trait in self._data.items()
        if trait and trait_id
    }

validate()

Validate the representation.

This method will validate all the traits in the representation.

Raises:

Type Description
TraitValidationError

If the trait is invalid within representation

Source code in client/ayon_core/pipeline/traits/representation.py
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
def validate(self) -> None:
    """Validate the representation.

    This method will validate all the traits in the representation.

    Raises:
        TraitValidationError: If the trait is invalid within representation

    """
    errors = []
    for trait in self._data.values():
        # we do this in the loop to catch all the errors
        try:
            trait.validate_trait(self)
        except TraitValidationError as e:  # noqa: PERF203
            errors.append(str(e))
    if errors:
        msg = "\n".join(errors)
        scope = self.name
        raise TraitValidationError(scope, msg)

RootlessLocation dataclass

Bases: TraitBase

RootlessLocation trait model.

RootlessLocation trait is a trait that represents a file path that is without a specific root. To get the 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 Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

rootless_path str

Rootless path.

Source code in client/ayon_core/pipeline/traits/content.py
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
@dataclass
class RootlessLocation(TraitBase):
    """RootlessLocation trait model.

    RootlessLocation trait is a trait that represents a file path that is
    without a specific root. To get the 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.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with version
        rootless_path (str): Rootless path.
    """

    name: ClassVar[str] = "RootlessLocation"
    description: ClassVar[str] = "RootlessLocation Trait Model"
    id: ClassVar[str] = "ayon.content.RootlessLocation.v1"
    persistent: ClassVar[bool] = True
    rootless_path: str

SMPTETimecode dataclass

Bases: TraitBase

SMPTE Timecode trait model.

Attributes:

Name Type Description
timecode str

SMPTE Timecode HH:MM:SS:FF

Source code in client/ayon_core/pipeline/traits/temporal.py
432
433
434
435
436
437
438
439
440
441
442
443
444
@dataclass
class SMPTETimecode(TraitBase):
    """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"
    persistent: ClassVar[bool] = True
    timecode: str

Sequence dataclass

Bases: TraitBase

Sequence trait model.

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 Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with a version

gaps_policy GapPolicy

Gaps policy - how to handle gaps in sequence.

frame_padding int

Frame padding.

frame_regex str

Frame regex - regular expression to match frame numbers. Must include 'index' named group and 'padding' named group.

frame_spec str

Frame list specification of frames. This takes string like "1-10,20-30,40-50" etc.

Source code in client/ayon_core/pipeline/traits/temporal.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
@dataclass
class Sequence(TraitBase):
    """Sequence trait model.

    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 a namespaced trait name with a version
        gaps_policy (GapPolicy): Gaps policy - how to handle gaps in
            sequence.
        frame_padding (int): Frame padding.
        frame_regex (str): Frame regex - regular expression to match
            frame numbers. Must include 'index' named group and 'padding'
            named group.
        frame_spec (str): Frame list specification of frames. This takes
            string like "1-10,20-30,40-50" etc.
    """

    name: ClassVar[str] = "Sequence"
    description: ClassVar[str] = "Sequence Trait Model"
    id: ClassVar[str] = "ayon.time.Sequence.v1"
    persistent: ClassVar[bool] = True
    frame_padding: int
    gaps_policy: Optional[GapPolicy] = GapPolicy.forbidden
    frame_regex: Optional[Pattern] = None
    frame_spec: Optional[str] = None

    @classmethod
    def validate_frame_regex(
        cls, v: Optional[Pattern]
    ) -> Optional[Pattern]:
        """Validate frame regex.

        Frame regex must have index and padding named groups.

        Returns:
            Optional[Pattern]: Compiled regex pattern.

        Raises:
            ValueError: If frame regex does not include 'index' and 'padding'

        """
        if v is None:
            return v
        if v and any(s not in v.pattern for s in ["?P<index>", "?P<padding>"]):
            msg = "Frame regex must include 'index' and `padding named groups"
            raise ValueError(msg)
        return v

    def validate_trait(self, representation: Representation) -> None:
        """Validate the trait."""
        super().validate_trait(representation)

        # if there is a FileLocations trait, run validation
        # on it as well

        with contextlib.suppress(MissingTraitError):
            self._validate_file_locations(representation)

    def _validate_file_locations(self, representation: Representation) -> None:
        """Validate file locations trait.

        If along with the Sequence trait, there is a FileLocations trait,
        then we need to validate if the file locations match the frame
        list specification.

        Args:
            representation (Representation): Representation instance.

        """
        from .content import FileLocations
        file_locs: FileLocations = representation.get_trait(
            FileLocations)
        # Validate if the file locations on representation
        # match the frame list (if any).
        # We need to extend the expected frames with Handles.
        frame_start = None
        frame_end = None
        handles_frame_start = None
        handles_frame_end = None
        with contextlib.suppress(MissingTraitError):
            handles: Handles = representation.get_trait(Handles)
            # if handles are inclusive, they should be already
            #  accounted for in the FrameRaged frame spec
            if not handles.inclusive:
                handles_frame_start = handles.frame_start_handle
                handles_frame_end = handles.frame_end_handle
        with contextlib.suppress(MissingTraitError):
            frame_ranged: FrameRanged = representation.get_trait(
                FrameRanged)
            frame_start = frame_ranged.frame_start
            frame_end = frame_ranged.frame_end
        if self.frame_spec is not None:
            self.validate_frame_list(
                file_locs,
                frame_start,
                frame_end,
                handles_frame_start,
                handles_frame_end)

        self.validate_frame_padding(file_locs)

    def validate_frame_list(
            self,
            file_locations: FileLocations,
            frame_start: Optional[int] = None,
            frame_end: Optional[int] = None,
            handles_frame_start: Optional[int] = None,
            handles_frame_end: Optional[int] = None) -> None:
        """Validate a frame list.

        This will take FileLocations trait and validate if the
        file locations match the frame list specification.

        For example, if the frame list is "1-10,20-30,40-50", then
        the frame numbers in the file locations should match
        these frames.

        It will skip the validation if the frame list is not provided.

        Args:
            file_locations (FileLocations): File locations trait.
            frame_start (Optional[int]): Frame start.
            frame_end (Optional[int]): Frame end.
            handles_frame_start (Optional[int]): Frame start handle.
            handles_frame_end (Optional[int]): Frame end handle.

        Raises:
            TraitValidationError: If the frame list does not match
                the expected frames.

        """
        if self.frame_spec is None:
            return

        frames: list[int] = []
        if self.frame_regex:
            frames = self.get_frame_list(
                    file_locations, self.frame_regex)
        else:
            frames = self.get_frame_list(
                    file_locations)

        expected_frames = self.list_spec_to_frames(self.frame_spec)
        if frame_start is None or frame_end is None:
            if min(expected_frames) != frame_start:
                msg = (
                    "Frame start does not match the expected frame start. "
                    f"Expected: {frame_start}, Found: {min(expected_frames)}"
                )
                raise TraitValidationError(self.name, msg)

            if max(expected_frames) != frame_end:
                msg = (
                    "Frame end does not match the expected frame end. "
                    f"Expected: {frame_end}, Found: {max(expected_frames)}"
                )
                raise TraitValidationError(self.name, msg)

        # we need to extend the expected frames with Handles
        if handles_frame_start is not None:
            expected_frames.extend(
                range(
                    min(frames) - handles_frame_start, min(frames) + 1))

        if handles_frame_end is not None:
            expected_frames.extend(
                range(
                    max(frames), max(frames) + handles_frame_end + 1))

        if set(frames) != set(expected_frames):
            msg = (
                "Frame list does not match the expected frames. "
                f"Expected: {expected_frames}, Found: {frames}"
            )
            raise TraitValidationError(self.name, msg)

    def validate_frame_padding(
            self, file_locations: FileLocations) -> None:
        """Validate frame padding.

        This will take FileLocations trait and validate if the
        frame padding matches the expected frame padding.

        Args:
            file_locations (FileLocations): File locations trait.

        Raises:
            TraitValidationError: If frame padding does not match
                the expected frame padding.

        """
        expected_padding = self.get_frame_padding(file_locations)
        if self.frame_padding != expected_padding:
            msg = (
                "Frame padding does not match the expected frame padding. "
                f"Expected: {expected_padding}, Found: {self.frame_padding}"
            )
            raise TraitValidationError(self.name, msg)

    @staticmethod
    def list_spec_to_frames(list_spec: str) -> list[int]:
        """Convert list specification to frames.

        Returns:
            list[int]: List of frame numbers.

        Raises:
            ValueError: If invalid frame number in the list.

        """
        frames = []
        segments = list_spec.split(",")
        for segment in segments:
            ranges = segment.split("-")
            if len(ranges) == 1:
                if not ranges[0].isdigit():
                    msg = (
                        "Invalid frame number "
                        f"in the list: {ranges[0]}"
                    )
                    raise ValueError(msg)
                frames.append(int(ranges[0]))
                continue
            start, end = segment.split("-")
            frames.extend(range(int(start), int(end) + 1))
        return frames

    @staticmethod
    def _get_collection(
        file_locations: FileLocations,
        regex: Optional[Pattern] = None) -> clique.Collection:
        r"""Get the collection from file locations.

        Args:
            file_locations (FileLocations): File locations trait.
            regex (Optional[Pattern]): Regular expression to match
                frame numbers. This is passed to ``clique.assemble()``.
                Default clique pattern is::

                    \.(?P<index>(?P<padding>0*)\d+)\.\D+\d?$

        Returns:
            clique.Collection: Collection instance.

        Raises:
            ValueError: If zero or multiple of collections are found.

        """
        patterns = [regex] if regex else None
        files: list[str] = [
            file.file_path.as_posix()
            for file in file_locations.file_paths
        ]
        src_collections, _ = clique.assemble(files, patterns=patterns)
        if len(src_collections) != 1:
            msg = (
                f"Zero or multiple collections found: {len(src_collections)} "
                "expected 1"
            )
            raise ValueError(msg)
        return src_collections[0]

    @staticmethod
    def get_frame_padding(file_locations: FileLocations) -> int:
        """Get frame padding.

        Returns:
            int: Frame padding.

        """
        src_collection = Sequence._get_collection(file_locations)
        padding = src_collection.padding
        # sometimes Clique doesn't get the padding right, so
        # we need to calculate it manually
        if padding == 0:
            padding = len(str(max(src_collection.indexes)))

        return padding

    @staticmethod
    def get_frame_list(
            file_locations: FileLocations,
            regex: Optional[Pattern] = None,
        ) -> list[int]:
        r"""Get the frame list.

        Args:
            file_locations (FileLocations): File locations trait.
            regex (Optional[Pattern]): Regular expression to match
                frame numbers. This is passed to ``clique.assemble()``.
                Default clique pattern is::

                    \.(?P<index>(?P<padding>0*)\d+)\.\D+\d?$

        Returns:
            list[int]: List of frame numbers.

        """
        src_collection = Sequence._get_collection(file_locations, regex)
        return list(src_collection.indexes)

    def get_frame_pattern(self) -> Pattern:
        """Get frame regex as a pattern.

        If the regex is a string, it will compile it to the pattern.

        Returns:
            Pattern: Compiled regex pattern.

        """
        if self.frame_regex:
            if isinstance(self.frame_regex, str):
                return re.compile(self.frame_regex)
            return self.frame_regex
        return re.compile(
            r"\.(?P<index>(?P<padding>0*)\d+)\.\D+\d?$")

get_frame_list(file_locations, regex=None) staticmethod

Get the frame list.

Parameters:

Name Type Description Default
file_locations FileLocations

File locations trait.

required
regex Optional[Pattern]

Regular expression to match frame numbers. This is passed to clique.assemble(). Default clique pattern is::

\.(?P<index>(?P<padding>0*)\d+)\.\D+\d?$
None

Returns:

Type Description
list[int]

list[int]: List of frame numbers.

Source code in client/ayon_core/pipeline/traits/temporal.py
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
@staticmethod
def get_frame_list(
        file_locations: FileLocations,
        regex: Optional[Pattern] = None,
    ) -> list[int]:
    r"""Get the frame list.

    Args:
        file_locations (FileLocations): File locations trait.
        regex (Optional[Pattern]): Regular expression to match
            frame numbers. This is passed to ``clique.assemble()``.
            Default clique pattern is::

                \.(?P<index>(?P<padding>0*)\d+)\.\D+\d?$

    Returns:
        list[int]: List of frame numbers.

    """
    src_collection = Sequence._get_collection(file_locations, regex)
    return list(src_collection.indexes)

get_frame_padding(file_locations) staticmethod

Get frame padding.

Returns:

Name Type Description
int int

Frame padding.

Source code in client/ayon_core/pipeline/traits/temporal.py
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
@staticmethod
def get_frame_padding(file_locations: FileLocations) -> int:
    """Get frame padding.

    Returns:
        int: Frame padding.

    """
    src_collection = Sequence._get_collection(file_locations)
    padding = src_collection.padding
    # sometimes Clique doesn't get the padding right, so
    # we need to calculate it manually
    if padding == 0:
        padding = len(str(max(src_collection.indexes)))

    return padding

get_frame_pattern()

Get frame regex as a pattern.

If the regex is a string, it will compile it to the pattern.

Returns:

Name Type Description
Pattern Pattern

Compiled regex pattern.

Source code in client/ayon_core/pipeline/traits/temporal.py
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
def get_frame_pattern(self) -> Pattern:
    """Get frame regex as a pattern.

    If the regex is a string, it will compile it to the pattern.

    Returns:
        Pattern: Compiled regex pattern.

    """
    if self.frame_regex:
        if isinstance(self.frame_regex, str):
            return re.compile(self.frame_regex)
        return self.frame_regex
    return re.compile(
        r"\.(?P<index>(?P<padding>0*)\d+)\.\D+\d?$")

list_spec_to_frames(list_spec) staticmethod

Convert list specification to frames.

Returns:

Type Description
list[int]

list[int]: List of frame numbers.

Raises:

Type Description
ValueError

If invalid frame number in the list.

Source code in client/ayon_core/pipeline/traits/temporal.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
@staticmethod
def list_spec_to_frames(list_spec: str) -> list[int]:
    """Convert list specification to frames.

    Returns:
        list[int]: List of frame numbers.

    Raises:
        ValueError: If invalid frame number in the list.

    """
    frames = []
    segments = list_spec.split(",")
    for segment in segments:
        ranges = segment.split("-")
        if len(ranges) == 1:
            if not ranges[0].isdigit():
                msg = (
                    "Invalid frame number "
                    f"in the list: {ranges[0]}"
                )
                raise ValueError(msg)
            frames.append(int(ranges[0]))
            continue
        start, end = segment.split("-")
        frames.extend(range(int(start), int(end) + 1))
    return frames

validate_frame_list(file_locations, frame_start=None, frame_end=None, handles_frame_start=None, handles_frame_end=None)

Validate a frame list.

This will take FileLocations trait and validate if the file locations match the frame list specification.

For example, if the frame list is "1-10,20-30,40-50", then the frame numbers in the file locations should match these frames.

It will skip the validation if the frame list is not provided.

Parameters:

Name Type Description Default
file_locations FileLocations

File locations trait.

required
frame_start Optional[int]

Frame start.

None
frame_end Optional[int]

Frame end.

None
handles_frame_start Optional[int]

Frame start handle.

None
handles_frame_end Optional[int]

Frame end handle.

None

Raises:

Type Description
TraitValidationError

If the frame list does not match the expected frames.

Source code in client/ayon_core/pipeline/traits/temporal.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
def validate_frame_list(
        self,
        file_locations: FileLocations,
        frame_start: Optional[int] = None,
        frame_end: Optional[int] = None,
        handles_frame_start: Optional[int] = None,
        handles_frame_end: Optional[int] = None) -> None:
    """Validate a frame list.

    This will take FileLocations trait and validate if the
    file locations match the frame list specification.

    For example, if the frame list is "1-10,20-30,40-50", then
    the frame numbers in the file locations should match
    these frames.

    It will skip the validation if the frame list is not provided.

    Args:
        file_locations (FileLocations): File locations trait.
        frame_start (Optional[int]): Frame start.
        frame_end (Optional[int]): Frame end.
        handles_frame_start (Optional[int]): Frame start handle.
        handles_frame_end (Optional[int]): Frame end handle.

    Raises:
        TraitValidationError: If the frame list does not match
            the expected frames.

    """
    if self.frame_spec is None:
        return

    frames: list[int] = []
    if self.frame_regex:
        frames = self.get_frame_list(
                file_locations, self.frame_regex)
    else:
        frames = self.get_frame_list(
                file_locations)

    expected_frames = self.list_spec_to_frames(self.frame_spec)
    if frame_start is None or frame_end is None:
        if min(expected_frames) != frame_start:
            msg = (
                "Frame start does not match the expected frame start. "
                f"Expected: {frame_start}, Found: {min(expected_frames)}"
            )
            raise TraitValidationError(self.name, msg)

        if max(expected_frames) != frame_end:
            msg = (
                "Frame end does not match the expected frame end. "
                f"Expected: {frame_end}, Found: {max(expected_frames)}"
            )
            raise TraitValidationError(self.name, msg)

    # we need to extend the expected frames with Handles
    if handles_frame_start is not None:
        expected_frames.extend(
            range(
                min(frames) - handles_frame_start, min(frames) + 1))

    if handles_frame_end is not None:
        expected_frames.extend(
            range(
                max(frames), max(frames) + handles_frame_end + 1))

    if set(frames) != set(expected_frames):
        msg = (
            "Frame list does not match the expected frames. "
            f"Expected: {expected_frames}, Found: {frames}"
        )
        raise TraitValidationError(self.name, msg)

validate_frame_padding(file_locations)

Validate frame padding.

This will take FileLocations trait and validate if the frame padding matches the expected frame padding.

Parameters:

Name Type Description Default
file_locations FileLocations

File locations trait.

required

Raises:

Type Description
TraitValidationError

If frame padding does not match the expected frame padding.

Source code in client/ayon_core/pipeline/traits/temporal.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
def validate_frame_padding(
        self, file_locations: FileLocations) -> None:
    """Validate frame padding.

    This will take FileLocations trait and validate if the
    frame padding matches the expected frame padding.

    Args:
        file_locations (FileLocations): File locations trait.

    Raises:
        TraitValidationError: If frame padding does not match
            the expected frame padding.

    """
    expected_padding = self.get_frame_padding(file_locations)
    if self.frame_padding != expected_padding:
        msg = (
            "Frame padding does not match the expected frame padding. "
            f"Expected: {expected_padding}, Found: {self.frame_padding}"
        )
        raise TraitValidationError(self.name, msg)

validate_frame_regex(v) classmethod

Validate frame regex.

Frame regex must have index and padding named groups.

Returns:

Type Description
Optional[Pattern]

Optional[Pattern]: Compiled regex pattern.

Raises:

Type Description
ValueError

If frame regex does not include 'index' and 'padding'

Source code in client/ayon_core/pipeline/traits/temporal.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@classmethod
def validate_frame_regex(
    cls, v: Optional[Pattern]
) -> Optional[Pattern]:
    """Validate frame regex.

    Frame regex must have index and padding named groups.

    Returns:
        Optional[Pattern]: Compiled regex pattern.

    Raises:
        ValueError: If frame regex does not include 'index' and 'padding'

    """
    if v is None:
        return v
    if v and any(s not in v.pattern for s in ["?P<index>", "?P<padding>"]):
        msg = "Frame regex must include 'index' and `padding named groups"
        raise ValueError(msg)
    return v

validate_trait(representation)

Validate the trait.

Source code in client/ayon_core/pipeline/traits/temporal.py
161
162
163
164
165
166
167
168
169
def validate_trait(self, representation: Representation) -> None:
    """Validate the trait."""
    super().validate_trait(representation)

    # if there is a FileLocations trait, run validation
    # on it as well

    with contextlib.suppress(MissingTraitError):
        self._validate_file_locations(representation)

Shader dataclass

Bases: TraitBase

Shader trait model.

Type trait for shader data.

Sync with OpenAssetIO MediaCreation Traits.

Source code in client/ayon_core/pipeline/traits/three_dimensional.py
53
54
55
56
57
58
59
60
61
62
63
64
65
@dataclass
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."
    persistent: ClassVar[bool] = True

SourceApplication dataclass

Bases: TraitBase

Metadata about the source (producing) application.

This can be useful in cases where this information is needed, but it cannot be determined from other means - like .txt files used for various motion tracking applications that must be interpreted by the loader.

Note that this is not really connected to any logic in ayon-applications addon.

Attributes:

Name Type Description
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.

Source code in client/ayon_core/pipeline/traits/meta.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
@dataclass
class SourceApplication(TraitBase):
    """Metadata about the source (producing) application.

    This can be useful in cases where this information is
    needed, but it cannot be determined from other means - like
    .txt files used for various motion tracking applications that
    must be interpreted by the loader.

    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"
    persistent: ClassVar[bool] = True
    application: str
    variant: Optional[str] = None
    version: Optional[str] = None
    platform: Optional[str] = None
    host_name: Optional[str] = None

Spatial dataclass

Bases: 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:

Name Type Description
up_axis str

Up axis.

handedness str

Handedness.

meters_per_unit float

Meters per unit.

Source code in client/ayon_core/pipeline/traits/three_dimensional.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@dataclass
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.
        meters_per_unit (float): Meters per unit.
    """

    id: ClassVar[str] = "ayon.3d.Spatial.v1"
    name: ClassVar[str] = "Spatial"
    description: ClassVar[str] = "Spatial trait model."
    persistent: ClassVar[bool] = True
    up_axis: str
    handedness: str
    meters_per_unit: float

Static dataclass

Bases: TraitBase

Static time trait.

Used to define static time (single frame).

Source code in client/ayon_core/pipeline/traits/temporal.py
447
448
449
450
451
452
453
454
455
456
457
@dataclass
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"
    persistent: ClassVar[bool] = True

Tagged dataclass

Bases: TraitBase

Tagged trait model.

This trait can hold a list of tags.

Example::

Tagged(tags=["tag1", "tag2"])

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

tags List[str]

Tags.

Source code in client/ayon_core/pipeline/traits/meta.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@dataclass
class Tagged(TraitBase):
    """Tagged trait model.

    This trait can hold a list of tags.

    Example::

        Tagged(tags=["tag1", "tag2"])

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with version
        tags (List[str]): Tags.
    """

    name: ClassVar[str] = "Tagged"
    description: ClassVar[str] = "Tagged Trait Model"
    id: ClassVar[str] = "ayon.meta.Tagged.v1"
    persistent: ClassVar[bool] = True
    tags: List[str]

TemplatePath dataclass

Bases: TraitBase

TemplatePath trait model.

This model represents a template path with formatting data. Template path can be an Anatomy template and data is used to format it.

Example::

TemplatePath(template="path/{key}/file", data={"key": "to"})

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

template str

Template path.

data dict[str]

Formatting data.

Source code in client/ayon_core/pipeline/traits/meta.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@dataclass
class TemplatePath(TraitBase):
    """TemplatePath trait model.

    This model represents a template path with formatting data.
    Template path can be an 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 a namespaced trait name with version
        template (str): Template path.
        data (dict[str]): Formatting data.
    """

    name: ClassVar[str] = "TemplatePath"
    description: ClassVar[str] = "Template Path Trait Model"
    id: ClassVar[str] = "ayon.meta.TemplatePath.v1"
    persistent: ClassVar[bool] = True
    template: str
    data: dict

TraitBase dataclass

Bases: ABC

Base trait model.

This model must be used as a base for all trait models. id, name, and description are abstract attributes that must be implemented in the derived classes.

Source code in client/ayon_core/pipeline/traits/trait.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@dataclass
class TraitBase(ABC):
    """Base trait model.

    This model must be used as a base for all trait models.
    ``id``, ``name``, and ``description`` are abstract attributes that must be
    implemented in the derived classes.
    """

    @property
    @abstractmethod
    def id(self) -> str:
        """Abstract attribute for ID."""
        ...

    @property
    @abstractmethod
    def name(self) -> str:
        """Abstract attribute for name."""
        ...

    @property
    @abstractmethod
    def description(self) -> str:
        """Abstract attribute for description."""
        ...

    def validate_trait(self, representation: Representation) -> None:  # noqa: PLR6301
        """Validate the trait.

        This method should be implemented in the derived classes to validate
        the trait data. It can be used by traits to validate against other
        traits in the representation.

        Args:
            representation (Representation): Representation instance.

        """
        return

    @classmethod
    def get_version(cls) -> Optional[int]:
        # sourcery skip: use-named-expression
        """Get a trait version from ID.

        This assumes Trait ID ends with `.v{version}`. If not, it will
        return None.

        Returns:
            Optional[int]: Trait version

        """
        version_regex = r"v(\d+)$"
        match = re.search(version_regex, str(cls.id))
        return int(match[1]) if match else None

    @classmethod
    def get_versionless_id(cls) -> str:
        """Get a trait ID without a version.

        Returns:
            str: Trait ID without a version.

        """
        return re.sub(r"\.v\d+$", "", str(cls.id))

    def as_dict(self) -> dict:
        """Return a trait as a dictionary.

        Returns:
            dict: Trait as dictionary.

        """
        return asdict(self)

description abstractmethod property

Abstract attribute for description.

id abstractmethod property

Abstract attribute for ID.

name abstractmethod property

Abstract attribute for name.

as_dict()

Return a trait as a dictionary.

Returns:

Name Type Description
dict dict

Trait as dictionary.

Source code in client/ayon_core/pipeline/traits/trait.py
82
83
84
85
86
87
88
89
def as_dict(self) -> dict:
    """Return a trait as a dictionary.

    Returns:
        dict: Trait as dictionary.

    """
    return asdict(self)

get_version() classmethod

Get a trait version from ID.

This assumes Trait ID ends with .v{version}. If not, it will return None.

Returns:

Type Description
Optional[int]

Optional[int]: Trait version

Source code in client/ayon_core/pipeline/traits/trait.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@classmethod
def get_version(cls) -> Optional[int]:
    # sourcery skip: use-named-expression
    """Get a trait version from ID.

    This assumes Trait ID ends with `.v{version}`. If not, it will
    return None.

    Returns:
        Optional[int]: Trait version

    """
    version_regex = r"v(\d+)$"
    match = re.search(version_regex, str(cls.id))
    return int(match[1]) if match else None

get_versionless_id() classmethod

Get a trait ID without a version.

Returns:

Name Type Description
str str

Trait ID without a version.

Source code in client/ayon_core/pipeline/traits/trait.py
72
73
74
75
76
77
78
79
80
@classmethod
def get_versionless_id(cls) -> str:
    """Get a trait ID without a version.

    Returns:
        str: Trait ID without a version.

    """
    return re.sub(r"\.v\d+$", "", str(cls.id))

validate_trait(representation)

Validate the trait.

This method should be implemented in the derived classes to validate the trait data. It can be used by traits to validate against other traits in the representation.

Parameters:

Name Type Description Default
representation Representation

Representation instance.

required
Source code in client/ayon_core/pipeline/traits/trait.py
43
44
45
46
47
48
49
50
51
52
53
54
def validate_trait(self, representation: Representation) -> None:  # noqa: PLR6301
    """Validate the trait.

    This method should be implemented in the derived classes to validate
    the trait data. It can be used by traits to validate against other
    traits in the representation.

    Args:
        representation (Representation): Representation instance.

    """
    return

TraitValidationError

Bases: Exception

Trait validation error exception.

This exception is raised when the trait validation fails.

Source code in client/ayon_core/pipeline/traits/trait.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class TraitValidationError(Exception):
    """Trait validation error exception.

    This exception is raised when the trait validation fails.
    """

    def __init__(self, scope: str, message: str):
        """Initialize the exception.

        We could determine the scope from the stack in the future,
        provided the scope is always Trait name.

        Args:
            scope (str): Scope of the error.
            message (str): Error message.

        """
        super().__init__(f"{scope}: {message}")

__init__(scope, message)

Initialize the exception.

We could determine the scope from the stack in the future, provided the scope is always Trait name.

Parameters:

Name Type Description Default
scope str

Scope of the error.

required
message str

Error message.

required
Source code in client/ayon_core/pipeline/traits/trait.py
129
130
131
132
133
134
135
136
137
138
139
140
def __init__(self, scope: str, message: str):
    """Initialize the exception.

    We could determine the scope from the stack in the future,
    provided the scope is always Trait name.

    Args:
        scope (str): Scope of the error.
        message (str): Error message.

    """
    super().__init__(f"{scope}: {message}")

Transient dataclass

Bases: TraitBase

Transient trait model.

Transient trait marks representation as transient. Such representations are not persisted in the system.

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with the version

Source code in client/ayon_core/pipeline/traits/lifecycle.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@dataclass
class Transient(TraitBase):
    """Transient trait model.

    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 a namespaced trait name with the version
    """

    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.

        Args:
            representation (Representation): Representation model.

        Raises:
            TraitValidationError: If representation is marked as both
                Persistent and Transient.

        """
        if representation.contains_trait(Persistent):
            msg = "Representation is marked as both Persistent and Transient."
            raise TraitValidationError(self.name, msg)

validate_trait(representation)

Validate representation is not Persistent.

Parameters:

Name Type Description Default
representation Representation

Representation model.

required

Raises:

Type Description
TraitValidationError

If representation is marked as both Persistent and Transient.

Source code in client/ayon_core/pipeline/traits/lifecycle.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def validate_trait(self, representation) -> None:  # noqa: ANN001
    """Validate representation is not Persistent.

    Args:
        representation (Representation): Representation model.

    Raises:
        TraitValidationError: If representation is marked as both
            Persistent and Transient.

    """
    if representation.contains_trait(Persistent):
        msg = "Representation is marked as both Persistent and Transient."
        raise TraitValidationError(self.name, msg)

UDIM dataclass

Bases: TraitBase

UDIM trait model.

This model represents a UDIM trait.

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be namespaced trait name with version

udim int

UDIM value.

udim_regex str

UDIM regex.

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
@dataclass
class UDIM(TraitBase):
    """UDIM trait model.

    This model represents a UDIM trait.

    Attributes:
        name (str): Trait name.
        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"
    persistent: ClassVar[bool] = True
    udim: list[int]
    udim_regex: Optional[str] = r"(?:\.|_)(?P<udim>\d+)\.\D+\d?$"

    # Field validator for udim_regex - this works in the pydantic model v2
    # but not with the pure data classes.
    @classmethod
    def validate_frame_regex(cls, v: Optional[str]) -> Optional[str]:
        """Validate udim regex.

        Returns:
            Optional[str]: UDIM regex.

        Raises:
            ValueError: UDIM regex must include 'udim' named group.

        """
        if v is not None and "?P<udim>" not in v:
            msg = "UDIM regex must include 'udim' named group"
            raise ValueError(msg)
        return v

    def get_file_location_for_udim(
            self,
            file_locations: FileLocations,
            udim: int,
        ) -> Optional[FileLocation]:
        """Get file location for UDIM.

        Args:
            file_locations (FileLocations): File locations.
            udim (int): UDIM value.

        Returns:
            Optional[FileLocation]: File location.

        """
        if not self.udim_regex:
            return None
        pattern = re.compile(self.udim_regex)
        for location in file_locations.file_paths:
            result = re.search(pattern, location.file_path.name)
            if result:
                udim_index = int(result.group("udim"))
                if udim_index == udim:
                    return location
        return None

    def get_udim_from_file_location(
            self, file_location: FileLocation) -> Optional[int]:
        """Get UDIM from the file location.

        Args:
            file_location (FileLocation): File location.

        Returns:
            Optional[int]: UDIM value.

        """
        if not self.udim_regex:
            return None
        pattern = re.compile(self.udim_regex)
        result = re.search(pattern, file_location.file_path.name)
        if result:
            return int(result.group("udim"))
        return None

get_file_location_for_udim(file_locations, udim)

Get file location for UDIM.

Parameters:

Name Type Description Default
file_locations FileLocations

File locations.

required
udim int

UDIM value.

required

Returns:

Type Description
Optional[FileLocation]

Optional[FileLocation]: File location.

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def get_file_location_for_udim(
        self,
        file_locations: FileLocations,
        udim: int,
    ) -> Optional[FileLocation]:
    """Get file location for UDIM.

    Args:
        file_locations (FileLocations): File locations.
        udim (int): UDIM value.

    Returns:
        Optional[FileLocation]: File location.

    """
    if not self.udim_regex:
        return None
    pattern = re.compile(self.udim_regex)
    for location in file_locations.file_paths:
        result = re.search(pattern, location.file_path.name)
        if result:
            udim_index = int(result.group("udim"))
            if udim_index == udim:
                return location
    return None

get_udim_from_file_location(file_location)

Get UDIM from the file location.

Parameters:

Name Type Description Default
file_location FileLocation

File location.

required

Returns:

Type Description
Optional[int]

Optional[int]: UDIM value.

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def get_udim_from_file_location(
        self, file_location: FileLocation) -> Optional[int]:
    """Get UDIM from the file location.

    Args:
        file_location (FileLocation): File location.

    Returns:
        Optional[int]: UDIM value.

    """
    if not self.udim_regex:
        return None
    pattern = re.compile(self.udim_regex)
    result = re.search(pattern, file_location.file_path.name)
    if result:
        return int(result.group("udim"))
    return None

validate_frame_regex(v) classmethod

Validate udim regex.

Returns:

Type Description
Optional[str]

Optional[str]: UDIM regex.

Raises:

Type Description
ValueError

UDIM regex must include 'udim' named group.

Source code in client/ayon_core/pipeline/traits/two_dimensional.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
@classmethod
def validate_frame_regex(cls, v: Optional[str]) -> Optional[str]:
    """Validate udim regex.

    Returns:
        Optional[str]: UDIM regex.

    Raises:
        ValueError: UDIM regex must include 'udim' named group.

    """
    if v is not None and "?P<udim>" not in v:
        msg = "UDIM regex must include 'udim' named group"
        raise ValueError(msg)
    return v

Variant dataclass

Bases: TraitBase

Variant trait model.

This model represents a variant of the representation.

Example::

Variant(variant="high")
Variant(variant="prores444)

Attributes:

Name Type Description
name str

Trait name.

description str

Trait description.

id str

id should be a namespaced trait name with version

variant str

Variant name.

Source code in client/ayon_core/pipeline/traits/meta.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@dataclass
class Variant(TraitBase):
    """Variant trait model.

    This model represents a variant of the representation.

    Example::

        Variant(variant="high")
        Variant(variant="prores444)

    Attributes:
        name (str): Trait name.
        description (str): Trait description.
        id (str): id should be a namespaced trait name with version
        variant (str): Variant name.
    """

    name: ClassVar[str] = "Variant"
    description: ClassVar[str] = "Variant Trait Model"
    id: ClassVar[str] = "ayon.meta.Variant.v1"
    persistent: ClassVar[bool] = True
    variant: str

get_sequence_from_files(paths)

Get the original frame range from files.

Note that this cannot guess frame rate, so it's set to 25. This will also fail on paths that cannot be assembled into one collection without any reminders.

Parameters:

Name Type Description Default
paths list[Path]

List of file paths.

required

Returns:

Name Type Description
FrameRanged FrameRanged

FrameRanged trait.

Raises:

Type Description
ValueError

If paths cannot be assembled into one collection

Source code in client/ayon_core/pipeline/traits/utils.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def get_sequence_from_files(paths: list[Path]) -> FrameRanged:
    """Get the original frame range from files.

    Note that this cannot guess frame rate, so it's set to 25.
    This will also fail on paths that cannot be assembled into
    one collection without any reminders.

    Args:
        paths (list[Path]): List of file paths.

    Returns:
        FrameRanged: FrameRanged trait.

    Raises:
        ValueError: If paths cannot be assembled into one collection

    """
    cols, rems = assemble([path.as_posix() for path in paths])
    if rems:
        msg = "Cannot assemble paths into one collection"
        raise ValueError(msg)
    if len(cols) != 1:
        msg = "More than one collection found"
        raise ValueError(msg)
    col = cols[0]

    sorted_frames = sorted(col.indexes)
    # First frame used for end value
    first_frame = sorted_frames[0]
    # Get last frame for padding
    last_frame = sorted_frames[-1]
    # Use padding from a collection of the last frame lengths as string
    # padding = max(col.padding, len(str(last_frame)))

    return FrameRanged(
        frame_start=first_frame, frame_end=last_frame,
        frames_per_second="25.0"
    )