Skip to content

representation

Defines the base trait model and representation.

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)