Skip to content

structures

AttributeValues

Container which keep values of Attribute definitions.

Goal is to have one object which hold values of attribute definitions for single instance.

Has dictionary like methods. Not all of them are allowed all the time.

Parameters:

Name Type Description Default
parent Union[CreatedInstance, PublishAttributes]

Parent object.

required
key str

Key of attribute values.

required
attr_defs List[AbstractAttrDef]

Definitions of value type and properties.

required
values dict

Values after possible conversion.

required
origin_data dict

Values loaded from host before conversion.

None
Source code in client/ayon_core/pipeline/create/structures.py
 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
class AttributeValues:
    """Container which keep values of Attribute definitions.

    Goal is to have one object which hold values of attribute definitions for
    single instance.

    Has dictionary like methods. Not all of them are allowed all the time.

    Args:
        parent (Union[CreatedInstance, PublishAttributes]): Parent object.
        key (str): Key of attribute values.
        attr_defs (List[AbstractAttrDef]): Definitions of value type
            and properties.
        values (dict): Values after possible conversion.
        origin_data (dict): Values loaded from host before conversion.

    """
    def __init__(self, parent, key, attr_defs, values, origin_data=None):
        self._parent = parent
        self._key = key
        if origin_data is None:
            origin_data = copy.deepcopy(values)
        self._origin_data = origin_data

        attr_defs_by_key = {
            attr_def.key: attr_def
            for attr_def in attr_defs
            if attr_def.is_value_def
        }
        for key, value in values.items():
            if key not in attr_defs_by_key:
                new_def = UnknownDef(key, label=key, default=value)
                attr_defs.append(new_def)
                attr_defs_by_key[key] = new_def

        self._attr_defs = attr_defs
        self._attr_defs_by_key = attr_defs_by_key

        self._data = {}
        for attr_def in attr_defs:
            value = values.get(attr_def.key)
            if value is None:
                continue
            converted_value = attr_def.convert_value(value)
            if converted_value == value:
                self._data[attr_def.key] = value

    def __setitem__(self, key, value):
        if key not in self._attr_defs_by_key:
            raise KeyError("Key \"{}\" was not found.".format(key))

        self.update({key: value})

    def __getitem__(self, key):
        if key not in self._attr_defs_by_key:
            return self._data[key]
        return self._data.get(key, self._attr_defs_by_key[key].default)

    def __contains__(self, key):
        return key in self._attr_defs_by_key

    def __iter__(self):
        for key in self._attr_defs_by_key:
            yield key

    def get(self, key, default=None):
        if key in self._attr_defs_by_key:
            return self[key]
        return default

    def keys(self):
        return self._attr_defs_by_key.keys()

    def values(self):
        for key in self._attr_defs_by_key.keys():
            yield self._data.get(key)

    def items(self):
        for key in self._attr_defs_by_key.keys():
            yield key, self._data.get(key)

    def get_attr_def(self, key, default=None):
        return self._attr_defs_by_key.get(key, default)

    def update(self, value):
        changes = {}
        for _key, _value in dict(value).items():
            if _key in self._data and self._data.get(_key) == _value:
                continue
            self._data[_key] = _value
            changes[_key] = _value

        if changes:
            self._parent.attribute_value_changed(self._key, changes)

    def pop(self, key, default=None):
        has_key = key in self._data
        value = self._data.pop(key, default)
        # Remove attribute definition if is 'UnknownDef'
        # - gives option to get rid of unknown values
        attr_def = self._attr_defs_by_key.get(key)
        if isinstance(attr_def, UnknownDef):
            self._attr_defs_by_key.pop(key)
            self._attr_defs.remove(attr_def)
        elif has_key:
            self._parent.attribute_value_changed(self._key, {key: None})
        return value

    def reset_values(self):
        self._data = {}

    def mark_as_stored(self):
        self._origin_data = copy.deepcopy(self._data)

    @property
    def attr_defs(self):
        """Pointer to attribute definitions.

        Returns:
            List[AbstractAttrDef]: Attribute definitions.
        """

        return list(self._attr_defs)

    @property
    def origin_data(self):
        return copy.deepcopy(self._origin_data)

    def data_to_store(self):
        """Create new dictionary with data to store.

        Returns:
            Dict[str, Any]: Attribute values that should be stored.
        """

        output = {}
        for key in self._data:
            output[key] = self[key]

        for key, attr_def in self._attr_defs_by_key.items():
            if key not in output:
                output[key] = attr_def.default
        return output

    def get_serialized_attr_defs(self):
        """Serialize attribute definitions to json serializable types.

        Returns:
            List[Dict[str, Any]]: Serialized attribute definitions.
        """

        return serialize_attr_defs(self._attr_defs)

attr_defs property

Pointer to attribute definitions.

Returns:

Type Description

List[AbstractAttrDef]: Attribute definitions.

data_to_store()

Create new dictionary with data to store.

Returns:

Type Description

Dict[str, Any]: Attribute values that should be stored.

Source code in client/ayon_core/pipeline/create/structures.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def data_to_store(self):
    """Create new dictionary with data to store.

    Returns:
        Dict[str, Any]: Attribute values that should be stored.
    """

    output = {}
    for key in self._data:
        output[key] = self[key]

    for key, attr_def in self._attr_defs_by_key.items():
        if key not in output:
            output[key] = attr_def.default
    return output

get_serialized_attr_defs()

Serialize attribute definitions to json serializable types.

Returns:

Type Description

List[Dict[str, Any]]: Serialized attribute definitions.

Source code in client/ayon_core/pipeline/create/structures.py
222
223
224
225
226
227
228
229
def get_serialized_attr_defs(self):
    """Serialize attribute definitions to json serializable types.

    Returns:
        List[Dict[str, Any]]: Serialized attribute definitions.
    """

    return serialize_attr_defs(self._attr_defs)

ConvertorItem

Item representing convertor plugin.

Parameters:

Name Type Description Default
identifier str

Identifier of convertor.

required
label str

Label which will be shown in UI.

required
Source code in client/ayon_core/pipeline/create/structures.py
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
class ConvertorItem:
    """Item representing convertor plugin.

    Args:
        identifier (str): Identifier of convertor.
        label (str): Label which will be shown in UI.
    """

    def __init__(self, identifier, label):
        self._id = str(uuid4())
        self.identifier = identifier
        self.label = label

    @property
    def id(self):
        return self._id

    def to_data(self):
        return {
            "id": self.id,
            "identifier": self.identifier,
            "label": self.label
        }

    @classmethod
    def from_data(cls, data):
        obj = cls(data["identifier"], data["label"])
        obj._id = data["id"]
        return obj

CreatedInstance

Instance entity with data that will be stored to workfile.

I think data must be required argument containing all minimum information about instance like "folderPath" and "task" and all data used for filling product name as creators may have custom data for product name filling.

Notes

Object have 2 possible initialization. One using 'creator' object which is recommended for api usage. Second by passing information about creator.

Parameters:

Name Type Description Default
product_type str

Product type that will be created.

required
product_name str

Name of product that will be created.

required
data Dict[str, Any]

Data used for filling product name or override data from already existing instance.

required
creator BaseCreator

Creator responsible for instance.

required
Source code in client/ayon_core/pipeline/create/structures.py
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
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
class CreatedInstance:
    """Instance entity with data that will be stored to workfile.

    I think `data` must be required argument containing all minimum information
    about instance like "folderPath" and "task" and all data used for filling
    product name as creators may have custom data for product name filling.

    Notes:
        Object have 2 possible initialization. One using 'creator' object which
            is recommended for api usage. Second by passing information about
            creator.

    Args:
        product_type (str): Product type that will be created.
        product_name (str): Name of product that will be created.
        data (Dict[str, Any]): Data used for filling product name or override
            data from already existing instance.
        creator (BaseCreator): Creator responsible for instance.
    """

    # Keys that can't be changed or removed from data after loading using
    #   creator.
    # - 'creator_attributes' and 'publish_attributes' can change values of
    #   their individual children but not on their own
    __immutable_keys = (
        "id",
        "instance_id",
        "productType",
        "creator_identifier",
        "creator_attributes",
        "publish_attributes"
    )
    # Keys that can be changed, but should not be removed from instance
    __required_keys = {
        "folderPath": None,
        "task": None,
        "productName": None,
        "active": True,
    }

    def __init__(
        self,
        product_type: str,
        product_name: str,
        data: Dict[str, Any],
        creator: "BaseCreator",
        transient_data: Optional[Dict[str, Any]] = None,
    ):
        self._creator = creator
        creator_identifier = creator.identifier
        group_label = creator.get_group_label()
        creator_label = creator.label

        self._creator_label = creator_label
        self._group_label = group_label or creator_identifier

        # Instance members may have actions on them
        # TODO implement members logic
        self._members = []

        # Data that can be used for lifetime of object
        if transient_data is None:
            transient_data = {}
        self._transient_data = transient_data

        # Create a copy of passed data to avoid changing them on the fly
        data = copy.deepcopy(data or {})

        # Pop dictionary values that will be converted to objects to be able
        #   catch changes
        orig_creator_attributes = data.pop("creator_attributes", None) or {}
        orig_publish_attributes = data.pop("publish_attributes", None) or {}

        # Store original value of passed data
        self._orig_data = copy.deepcopy(data)

        # Pop 'productType' and 'productName' to prevent unexpected changes
        data.pop("productType", None)
        data.pop("productName", None)
        # Backwards compatibility with OpenPype instances
        data.pop("family", None)
        data.pop("subset", None)

        asset_name = data.pop("asset", None)
        if "folderPath" not in data:
            data["folderPath"] = asset_name

        # QUESTION Does it make sense to have data stored as ordered dict?
        self._data = collections.OrderedDict()
        # QUESTION Do we need this "id" information on instance?
        item_id = data.get("id")
        # TODO use only 'AYON_INSTANCE_ID' when all hosts support it
        if item_id not in {AYON_INSTANCE_ID, AVALON_INSTANCE_ID}:
            item_id = AYON_INSTANCE_ID
        self._data["id"] = item_id
        self._data["productType"] = product_type
        self._data["productName"] = product_name
        self._data["active"] = data.get("active", True)
        self._data["creator_identifier"] = creator_identifier

        # Pop from source data all keys that are defined in `_data` before
        #   this moment and through their values away
        # - they should be the same and if are not then should not change
        #   already set values
        for key in self._data.keys():
            if key in data:
                data.pop(key)

        self._data["variant"] = self._data.get("variant") or ""
        # Stored creator specific attribute values
        # {key: value}
        creator_values = copy.deepcopy(orig_creator_attributes)

        self._data["creator_attributes"] = creator_values

        # Stored publish specific attribute values
        # {<plugin name>: {key: value}}
        self._data["publish_attributes"] = PublishAttributes(
            self, orig_publish_attributes
        )
        if data:
            self._data.update(data)

        for key, default in self.__required_keys.items():
            self._data.setdefault(key, default)

        if not self._data.get("instance_id"):
            self._data["instance_id"] = str(uuid4())

        creator_attr_defs = creator.get_attr_defs_for_instance(self)
        self.set_create_attr_defs(
            creator_attr_defs, creator_values
        )

    def __str__(self):
        return (
            "<CreatedInstance {product[name]}"
            " ({product[type]}[{creator_identifier}])> {data}"
        ).format(
            creator_identifier=self.creator_identifier,
            product={"name": self.product_name, "type": self.product_type},
            data=str(self._data)
        )

    # --- Dictionary like methods ---
    def __getitem__(self, key):
        return self._data[key]

    def __contains__(self, key):
        return key in self._data

    def __setitem__(self, key, value):
        # Validate immutable keys
        if key in self.__immutable_keys:
            if value == self._data.get(key):
                return
            # Raise exception if key is immutable and value has changed
            raise ImmutableKeyError(key)

        if key in self._data and self._data[key] == value:
            return

        self._data[key] = value
        self._create_context.instance_values_changed(
            self.id, {key: value}
        )

    def get(self, key, default=None):
        return self._data.get(key, default)

    def pop(self, key, *args, **kwargs):
        # Raise exception if is trying to pop key which is immutable
        if key in self.__immutable_keys:
            raise ImmutableKeyError(key)

        has_key = key in self._data
        output = self._data.pop(key, *args, **kwargs)
        if has_key:
            if key in self.__required_keys:
                self._data[key] = self.__required_keys[key]
            self._create_context.instance_values_changed(
                self.id, {key: None}
            )
        return output

    def keys(self):
        return self._data.keys()

    def values(self):
        return self._data.values()

    def items(self):
        return self._data.items()
    # ------

    @property
    def product_type(self):
        return self._data["productType"]

    @property
    def product_name(self):
        return self._data["productName"]

    @property
    def label(self):
        label = self._data.get("label")
        if not label:
            label = self.product_name
        return label

    @property
    def group_label(self):
        label = self._data.get("group")
        if label:
            return label
        return self._group_label

    @property
    def origin_data(self):
        output = copy.deepcopy(self._orig_data)
        output["creator_attributes"] = self.creator_attributes.origin_data
        output["publish_attributes"] = self.publish_attributes.origin_data
        return output

    @property
    def creator_identifier(self):
        return self._data["creator_identifier"]

    @property
    def creator_label(self):
        return self._creator.label or self.creator_identifier

    @property
    def id(self):
        """Instance identifier.

        Returns:
            str: UUID of instance.
        """

        return self._data["instance_id"]

    @property
    def data(self):
        """Legacy access to data.

        Access to data is needed to modify values.

        Returns:
            CreatedInstance: Object can be used as dictionary but with
                validations of immutable keys.
        """

        return self

    @property
    def transient_data(self):
        """Data stored for lifetime of instance object.

        These data are not stored to scene and will be lost on object
        deletion.

        Can be used to store objects. In some host implementations is not
        possible to reference to object in scene with some unique identifier
        (e.g. node in Fusion.). In that case it is handy to store the object
        here. Should be used that way only if instance data are stored on the
        node itself.

        Returns:
            Dict[str, Any]: Dictionary object where you can store data related
                to instance for lifetime of instance object.
        """

        return self._transient_data

    def changes(self):
        """Calculate and return changes."""

        return TrackChangesItem(self.origin_data, self.data_to_store())

    def mark_as_stored(self):
        """Should be called when instance data are stored.

        Origin data are replaced by current data so changes are cleared.
        """

        orig_keys = set(self._orig_data.keys())
        for key, value in self._data.items():
            orig_keys.discard(key)
            if key in ("creator_attributes", "publish_attributes"):
                continue
            self._orig_data[key] = copy.deepcopy(value)

        for key in orig_keys:
            self._orig_data.pop(key)

        self.creator_attributes.mark_as_stored()
        self.publish_attributes.mark_as_stored()

    @property
    def creator_attributes(self):
        return self._data["creator_attributes"]

    @property
    def creator_attribute_defs(self):
        """Attribute definitions defined by creator plugin.

        Returns:
              List[AbstractAttrDef]: Attribute definitions.
        """

        return self.creator_attributes.attr_defs

    @property
    def publish_attributes(self):
        return self._data["publish_attributes"]

    @property
    def has_promised_context(self) -> bool:
        """Get context data that are promised to be set by creator.

        Returns:
            bool: Has context that won't bo validated. Artist can't change
                value when set to True.

        """
        return self._transient_data.get("has_promised_context", False)

    def data_to_store(self):
        """Collect data that contain json parsable types.

        It is possible to recreate the instance using these data.

        Todos:
            We probably don't need OrderedDict. When data are loaded they
                are not ordered anymore.

        Returns:
            OrderedDict: Ordered dictionary with instance data.
        """

        output = collections.OrderedDict()
        for key, value in self._data.items():
            if key in ("creator_attributes", "publish_attributes"):
                continue
            output[key] = value

        if isinstance(self.creator_attributes, AttributeValues):
            creator_attributes = self.creator_attributes.data_to_store()
        else:
            creator_attributes = copy.deepcopy(self.creator_attributes)
        output["creator_attributes"] = creator_attributes
        output["publish_attributes"] = self.publish_attributes.data_to_store()

        return output

    def set_create_attr_defs(self, attr_defs, value=None):
        """Create plugin updates create attribute definitions.

        Method called by create plugin when attribute definitions should
            be changed.

        Args:
            attr_defs (List[AbstractAttrDef]): Attribute definitions.
            value (Optional[Dict[str, Any]]): Values of attribute definitions.
                Current values are used if not passed in.

        """
        if value is None:
            value = self._data["creator_attributes"]

        if isinstance(value, AttributeValues):
            value = value.data_to_store()

        if isinstance(self._data["creator_attributes"], AttributeValues):
            origin_data = self._data["creator_attributes"].origin_data
        else:
            origin_data = copy.deepcopy(self._data["creator_attributes"])
        self._data["creator_attributes"] = CreatorAttributeValues(
            self,
            "creator_attributes",
            attr_defs,
            value,
            origin_data
        )
        self._create_context.instance_create_attr_defs_changed(self.id)

    @classmethod
    def from_existing(
        cls,
        instance_data: Dict[str, Any],
        creator: "BaseCreator",
        transient_data: Optional[Dict[str, Any]] = None,
    ) -> "CreatedInstance":
        """Convert instance data from workfile to CreatedInstance.

        Args:
            instance_data (Dict[str, Any]): Data in a structure ready for
                'CreatedInstance' object.
            creator (BaseCreator): Creator plugin which is creating the
                instance of for which the instance belongs.
            transient_data (Optional[dict[str, Any]]): Instance transient
                data.

        Returns:
            CreatedInstance: Instance object.

        """
        instance_data = copy.deepcopy(instance_data)

        product_type = instance_data.get("productType")
        if product_type is None:
            product_type = instance_data.get("family")
            if product_type is None:
                product_type = creator.product_type
        product_name = instance_data.get("productName")
        if product_name is None:
            product_name = instance_data.get("subset")

        return cls(
            product_type,
            product_name,
            instance_data,
            creator,
            transient_data=transient_data,
        )

    def attribute_value_changed(self, key, changes):
        """A value changed.

        Args:
            key (str): Key of attribute values.
            changes (Dict[str, Any]): Changes in values.

        """
        self._create_context.instance_values_changed(self.id, {key: changes})

    def set_publish_plugin_attr_defs(self, plugin_name, attr_defs):
        """Set attribute definitions for publish plugin.

        Args:
            plugin_name(str): Name of publish plugin.
            attr_defs(List[AbstractAttrDef]): Attribute definitions.

        """
        self.publish_attributes.set_publish_plugin_attr_defs(
            plugin_name, attr_defs
        )
        self._create_context.instance_publish_attr_defs_changed(
            self.id, plugin_name
        )

    def publish_attribute_value_changed(self, plugin_name, value):
        """Method called from PublishAttributes.

        Args:
            plugin_name (str): Plugin name.
            value (Dict[str, Any]): Changes in values for the plugin.

        """
        self._create_context.instance_values_changed(
            self.id,
            {
                "publish_attributes": {
                    plugin_name: value,
                },
            },
        )

    def add_members(self, members):
        """Currently unused method."""

        for member in members:
            if member not in self._members:
                self._members.append(member)

    @property
    def _create_context(self):
        """Get create context.

        Returns:
            CreateContext: Context object which wraps object.

        """
        return self._creator.create_context

creator_attribute_defs property

Attribute definitions defined by creator plugin.

Returns:

Type Description

List[AbstractAttrDef]: Attribute definitions.

data property

Legacy access to data.

Access to data is needed to modify values.

Returns:

Name Type Description
CreatedInstance

Object can be used as dictionary but with validations of immutable keys.

has_promised_context property

Get context data that are promised to be set by creator.

Returns:

Name Type Description
bool bool

Has context that won't bo validated. Artist can't change value when set to True.

id property

Instance identifier.

Returns:

Name Type Description
str

UUID of instance.

transient_data property

Data stored for lifetime of instance object.

These data are not stored to scene and will be lost on object deletion.

Can be used to store objects. In some host implementations is not possible to reference to object in scene with some unique identifier (e.g. node in Fusion.). In that case it is handy to store the object here. Should be used that way only if instance data are stored on the node itself.

Returns:

Type Description

Dict[str, Any]: Dictionary object where you can store data related to instance for lifetime of instance object.

add_members(members)

Currently unused method.

Source code in client/ayon_core/pipeline/create/structures.py
878
879
880
881
882
883
def add_members(self, members):
    """Currently unused method."""

    for member in members:
        if member not in self._members:
            self._members.append(member)

attribute_value_changed(key, changes)

A value changed.

Parameters:

Name Type Description Default
key str

Key of attribute values.

required
changes Dict[str, Any]

Changes in values.

required
Source code in client/ayon_core/pipeline/create/structures.py
836
837
838
839
840
841
842
843
844
def attribute_value_changed(self, key, changes):
    """A value changed.

    Args:
        key (str): Key of attribute values.
        changes (Dict[str, Any]): Changes in values.

    """
    self._create_context.instance_values_changed(self.id, {key: changes})

changes()

Calculate and return changes.

Source code in client/ayon_core/pipeline/create/structures.py
684
685
686
687
def changes(self):
    """Calculate and return changes."""

    return TrackChangesItem(self.origin_data, self.data_to_store())

data_to_store()

Collect data that contain json parsable types.

It is possible to recreate the instance using these data.

Todos

We probably don't need OrderedDict. When data are loaded they are not ordered anymore.

Returns:

Name Type Description
OrderedDict

Ordered dictionary with instance data.

Source code in client/ayon_core/pipeline/create/structures.py
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
def data_to_store(self):
    """Collect data that contain json parsable types.

    It is possible to recreate the instance using these data.

    Todos:
        We probably don't need OrderedDict. When data are loaded they
            are not ordered anymore.

    Returns:
        OrderedDict: Ordered dictionary with instance data.
    """

    output = collections.OrderedDict()
    for key, value in self._data.items():
        if key in ("creator_attributes", "publish_attributes"):
            continue
        output[key] = value

    if isinstance(self.creator_attributes, AttributeValues):
        creator_attributes = self.creator_attributes.data_to_store()
    else:
        creator_attributes = copy.deepcopy(self.creator_attributes)
    output["creator_attributes"] = creator_attributes
    output["publish_attributes"] = self.publish_attributes.data_to_store()

    return output

from_existing(instance_data, creator, transient_data=None) classmethod

Convert instance data from workfile to CreatedInstance.

Parameters:

Name Type Description Default
instance_data Dict[str, Any]

Data in a structure ready for 'CreatedInstance' object.

required
creator BaseCreator

Creator plugin which is creating the instance of for which the instance belongs.

required
transient_data Optional[dict[str, Any]]

Instance transient data.

None

Returns:

Name Type Description
CreatedInstance CreatedInstance

Instance object.

Source code in client/ayon_core/pipeline/create/structures.py
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
@classmethod
def from_existing(
    cls,
    instance_data: Dict[str, Any],
    creator: "BaseCreator",
    transient_data: Optional[Dict[str, Any]] = None,
) -> "CreatedInstance":
    """Convert instance data from workfile to CreatedInstance.

    Args:
        instance_data (Dict[str, Any]): Data in a structure ready for
            'CreatedInstance' object.
        creator (BaseCreator): Creator plugin which is creating the
            instance of for which the instance belongs.
        transient_data (Optional[dict[str, Any]]): Instance transient
            data.

    Returns:
        CreatedInstance: Instance object.

    """
    instance_data = copy.deepcopy(instance_data)

    product_type = instance_data.get("productType")
    if product_type is None:
        product_type = instance_data.get("family")
        if product_type is None:
            product_type = creator.product_type
    product_name = instance_data.get("productName")
    if product_name is None:
        product_name = instance_data.get("subset")

    return cls(
        product_type,
        product_name,
        instance_data,
        creator,
        transient_data=transient_data,
    )

mark_as_stored()

Should be called when instance data are stored.

Origin data are replaced by current data so changes are cleared.

Source code in client/ayon_core/pipeline/create/structures.py
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
def mark_as_stored(self):
    """Should be called when instance data are stored.

    Origin data are replaced by current data so changes are cleared.
    """

    orig_keys = set(self._orig_data.keys())
    for key, value in self._data.items():
        orig_keys.discard(key)
        if key in ("creator_attributes", "publish_attributes"):
            continue
        self._orig_data[key] = copy.deepcopy(value)

    for key in orig_keys:
        self._orig_data.pop(key)

    self.creator_attributes.mark_as_stored()
    self.publish_attributes.mark_as_stored()

publish_attribute_value_changed(plugin_name, value)

Method called from PublishAttributes.

Parameters:

Name Type Description Default
plugin_name str

Plugin name.

required
value Dict[str, Any]

Changes in values for the plugin.

required
Source code in client/ayon_core/pipeline/create/structures.py
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
def publish_attribute_value_changed(self, plugin_name, value):
    """Method called from PublishAttributes.

    Args:
        plugin_name (str): Plugin name.
        value (Dict[str, Any]): Changes in values for the plugin.

    """
    self._create_context.instance_values_changed(
        self.id,
        {
            "publish_attributes": {
                plugin_name: value,
            },
        },
    )

set_create_attr_defs(attr_defs, value=None)

Create plugin updates create attribute definitions.

Method called by create plugin when attribute definitions should be changed.

Parameters:

Name Type Description Default
attr_defs List[AbstractAttrDef]

Attribute definitions.

required
value Optional[Dict[str, Any]]

Values of attribute definitions. Current values are used if not passed in.

None
Source code in client/ayon_core/pipeline/create/structures.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
def set_create_attr_defs(self, attr_defs, value=None):
    """Create plugin updates create attribute definitions.

    Method called by create plugin when attribute definitions should
        be changed.

    Args:
        attr_defs (List[AbstractAttrDef]): Attribute definitions.
        value (Optional[Dict[str, Any]]): Values of attribute definitions.
            Current values are used if not passed in.

    """
    if value is None:
        value = self._data["creator_attributes"]

    if isinstance(value, AttributeValues):
        value = value.data_to_store()

    if isinstance(self._data["creator_attributes"], AttributeValues):
        origin_data = self._data["creator_attributes"].origin_data
    else:
        origin_data = copy.deepcopy(self._data["creator_attributes"])
    self._data["creator_attributes"] = CreatorAttributeValues(
        self,
        "creator_attributes",
        attr_defs,
        value,
        origin_data
    )
    self._create_context.instance_create_attr_defs_changed(self.id)

set_publish_plugin_attr_defs(plugin_name, attr_defs)

Set attribute definitions for publish plugin.

Parameters:

Name Type Description Default
plugin_name(str)

Name of publish plugin.

required
attr_defs(List[AbstractAttrDef])

Attribute definitions.

required
Source code in client/ayon_core/pipeline/create/structures.py
846
847
848
849
850
851
852
853
854
855
856
857
858
859
def set_publish_plugin_attr_defs(self, plugin_name, attr_defs):
    """Set attribute definitions for publish plugin.

    Args:
        plugin_name(str): Name of publish plugin.
        attr_defs(List[AbstractAttrDef]): Attribute definitions.

    """
    self.publish_attributes.set_publish_plugin_attr_defs(
        plugin_name, attr_defs
    )
    self._create_context.instance_publish_attr_defs_changed(
        self.id, plugin_name
    )

CreatorAttributeValues

Bases: AttributeValues

Creator specific attribute values of an instance.

Source code in client/ayon_core/pipeline/create/structures.py
232
233
234
235
236
237
class CreatorAttributeValues(AttributeValues):
    """Creator specific attribute values of an instance."""

    @property
    def instance(self):
        return self._parent

InstanceMember

Representation of instance member.

TODO: Implement and use!

Source code in client/ayon_core/pipeline/create/structures.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class InstanceMember:
    """Representation of instance member.

    TODO:
    Implement and use!
    """

    def __init__(self, instance, name):
        self.instance = instance

        instance.add_members(self)

        self.name = name
        self._actions = []

    def add_action(self, label, callback):
        self._actions.append({
            "label": label,
            "callback": callback
        })

PublishAttributeValues

Bases: AttributeValues

Publish plugin specific attribute values.

Values are for single plugin which can be on CreatedInstance or context values stored on CreateContext.

Source code in client/ayon_core/pipeline/create/structures.py
240
241
242
243
244
245
246
247
248
249
class PublishAttributeValues(AttributeValues):
    """Publish plugin specific attribute values.

    Values are for single plugin which can be on `CreatedInstance`
    or context values stored on `CreateContext`.
    """

    @property
    def publish_attributes(self):
        return self._parent

PublishAttributes

Wrapper for publish plugin attribute definitions.

Cares about handling attribute definitions of multiple publish plugins. Keep information about attribute definitions and their values.

Parameters:

Name Type Description Default
parent(CreatedInstance, CreateContext

Parent for which will be data stored and from which are data loaded.

required
origin_data(dict)

Loaded data by plugin class name.

required
Source code in client/ayon_core/pipeline/create/structures.py
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
class PublishAttributes:
    """Wrapper for publish plugin attribute definitions.

    Cares about handling attribute definitions of multiple publish plugins.
    Keep information about attribute definitions and their values.

    Args:
        parent(CreatedInstance, CreateContext): Parent for which will be
            data stored and from which are data loaded.
        origin_data(dict): Loaded data by plugin class name.

    """
    def __init__(self, parent, origin_data):
        self._parent = parent
        self._origin_data = copy.deepcopy(origin_data)

        self._data = copy.deepcopy(origin_data)

    def __getitem__(self, key):
        return self._data[key]

    def __contains__(self, key):
        return key in self._data

    def keys(self):
        return self._data.keys()

    def values(self):
        return self._data.values()

    def items(self):
        return self._data.items()

    def get(self, key, default=None):
        return self._data.get(key, default)

    def pop(self, key, default=None):
        """Remove or reset value for plugin.

        Plugin values are reset to defaults if plugin is available but
        data of plugin which was not found are removed.

        Args:
            key(str): Plugin name.
            default: Default value if plugin was not found.
        """

        if key not in self._data:
            return default

        value = self._data[key]
        if not isinstance(value, AttributeValues):
            self.attribute_value_changed(key, None)
            return self._data.pop(key)

        value_item = self._data[key]
        # Prepare value to return
        output = value_item.data_to_store()
        # Reset values
        value_item.reset_values()
        self.attribute_value_changed(
            key, value_item.data_to_store()
        )
        return output

    def mark_as_stored(self):
        self._origin_data = copy.deepcopy(self.data_to_store())

    def data_to_store(self):
        """Convert attribute values to "data to store"."""
        output = {}
        for key, attr_value in self._data.items():
            if isinstance(attr_value, AttributeValues):
                output[key] = attr_value.data_to_store()
            else:
                output[key] = attr_value
        return output

    @property
    def origin_data(self):
        return copy.deepcopy(self._origin_data)

    def attribute_value_changed(self, key, changes):
        self._parent.publish_attribute_value_changed(key,  changes)

    def set_publish_plugin_attr_defs(
        self,
        plugin_name: str,
        attr_defs: List[AbstractAttrDef],
        value: Optional[Dict[str, Any]] = None
    ):
        """Set attribute definitions for plugin.

        Args:
            plugin_name (str): Name of plugin.
            attr_defs (List[AbstractAttrDef]): Attribute definitions.
            value (Optional[Dict[str, Any]]): Attribute values.

        """
        # TODO what if 'attr_defs' is 'None'?
        if value is None:
            value = self._data.get(plugin_name)

        if value is None:
            value = {}

        self._data[plugin_name] = PublishAttributeValues(
            self, plugin_name, attr_defs, value, value
        )

    def serialize_attributes(self):
        return {
            "attr_defs": {
                plugin_name: attrs_value.get_serialized_attr_defs()
                for plugin_name, attrs_value in self._data.items()
            },
        }

    def deserialize_attributes(self, data):
        attr_defs = deserialize_attr_defs(data["attr_defs"])

        origin_data = self._origin_data
        data = self._data
        self._data = {}

        added_keys = set()
        for plugin_name, attr_defs_data in attr_defs.items():
            attr_defs = deserialize_attr_defs(attr_defs_data)
            value = data.get(plugin_name) or {}
            orig_value = copy.deepcopy(origin_data.get(plugin_name) or {})
            self._data[plugin_name] = PublishAttributeValues(
                self, plugin_name, attr_defs, value, orig_value
            )

        for key, value in data.items():
            if key not in added_keys:
                self._data[key] = value

data_to_store()

Convert attribute values to "data to store".

Source code in client/ayon_core/pipeline/create/structures.py
320
321
322
323
324
325
326
327
328
def data_to_store(self):
    """Convert attribute values to "data to store"."""
    output = {}
    for key, attr_value in self._data.items():
        if isinstance(attr_value, AttributeValues):
            output[key] = attr_value.data_to_store()
        else:
            output[key] = attr_value
    return output

pop(key, default=None)

Remove or reset value for plugin.

Plugin values are reset to defaults if plugin is available but data of plugin which was not found are removed.

Parameters:

Name Type Description Default
key(str)

Plugin name.

required
default

Default value if plugin was not found.

None
Source code in client/ayon_core/pipeline/create/structures.py
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
def pop(self, key, default=None):
    """Remove or reset value for plugin.

    Plugin values are reset to defaults if plugin is available but
    data of plugin which was not found are removed.

    Args:
        key(str): Plugin name.
        default: Default value if plugin was not found.
    """

    if key not in self._data:
        return default

    value = self._data[key]
    if not isinstance(value, AttributeValues):
        self.attribute_value_changed(key, None)
        return self._data.pop(key)

    value_item = self._data[key]
    # Prepare value to return
    output = value_item.data_to_store()
    # Reset values
    value_item.reset_values()
    self.attribute_value_changed(
        key, value_item.data_to_store()
    )
    return output

set_publish_plugin_attr_defs(plugin_name, attr_defs, value=None)

Set attribute definitions for plugin.

Parameters:

Name Type Description Default
plugin_name str

Name of plugin.

required
attr_defs List[AbstractAttrDef]

Attribute definitions.

required
value Optional[Dict[str, Any]]

Attribute values.

None
Source code in client/ayon_core/pipeline/create/structures.py
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
def set_publish_plugin_attr_defs(
    self,
    plugin_name: str,
    attr_defs: List[AbstractAttrDef],
    value: Optional[Dict[str, Any]] = None
):
    """Set attribute definitions for plugin.

    Args:
        plugin_name (str): Name of plugin.
        attr_defs (List[AbstractAttrDef]): Attribute definitions.
        value (Optional[Dict[str, Any]]): Attribute values.

    """
    # TODO what if 'attr_defs' is 'None'?
    if value is None:
        value = self._data.get(plugin_name)

    if value is None:
        value = {}

    self._data[plugin_name] = PublishAttributeValues(
        self, plugin_name, attr_defs, value, value
    )