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
 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
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 = self._update(value)
        if changes:
            self._parent.attribute_value_changed(self._key, changes)

    def pop(self, key, default=None):
        value, changes = self._pop(key, default)
        if changes:
            self._parent.attribute_value_changed(self._key, changes)
        return value

    def set_value(self, value):
        pop_keys = set(value.keys()) - set(self._data.keys())
        changes = self._update(value)
        for key in pop_keys:
            _, key_changes = self._pop(key, None)
            changes.update(key_changes)

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

    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)

    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
        return changes

    def _pop(self, key, default):
        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)
        changes = {}
        if isinstance(attr_def, UnknownDef):
            self._attr_defs_by_key.pop(key)
            self._attr_defs.remove(attr_def)
        elif has_key:
            changes[key] = None
        return value, changes

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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
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
237
238
239
240
241
242
243
244
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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
 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
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
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
        self._is_mandatory: bool = False
        self._parent_instance_id: Optional[str] = None
        self._parent_flags: int = 0

        # 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

        if self.is_mandatory and key == "active" and value is not True:
            raise ImmutableKeyError(
                key,
                "Instance is mandatory and can't be disabled."
            )

        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

    @property
    def is_mandatory(self) -> bool:
        """Check if instance is mandatory.

        Returns:
            bool: True if instance is mandatory, False otherwise.

        """
        return self._is_mandatory

    def set_mandatory(self, value: bool) -> None:
        """Set instance as mandatory or not.

        Mandatory instance can't be disabled in UI.

        Args:
            value (bool): True if instance should be mandatory, False
                otherwise.

        """
        if value is self._is_mandatory:
            return
        self._is_mandatory = value
        if value is True:
            self["active"] = True
        self._create_context.instance_requirement_changed(self.id)

    @property
    def parent_instance_id(self) -> Optional[str]:
        return self._parent_instance_id

    @property
    def parent_flags(self) -> int:
        return self._parent_flags

    def set_parent(
        self, instance_id: Optional[str], flags: int
    ) -> None:
        """Set parent instance id and parenting flags.

        Args:
            instance_id (Optional[str]): Parent instance id.
            flags (int): Parenting flags.

        """
        changed = False
        if instance_id != self._parent_instance_id:
            changed = True
            self._parent_instance_id = instance_id

        if flags is None:
            flags = 0

        if self._parent_flags != flags:
            self._parent_flags = flags
            changed = True

        if changed:
            self._create_context.instance_parent_changed(self.id)

    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.

is_mandatory property

Check if instance is mandatory.

Returns:

Name Type Description
bool bool

True if instance is mandatory, False otherwise.

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
1002
1003
1004
1005
1006
1007
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
960
961
962
963
964
965
966
967
968
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
808
809
810
811
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
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
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
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
@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
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
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
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
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
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
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_mandatory(value)

Set instance as mandatory or not.

Mandatory instance can't be disabled in UI.

Parameters:

Name Type Description Default
value bool

True if instance should be mandatory, False otherwise.

required
Source code in client/ayon_core/pipeline/create/structures.py
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
def set_mandatory(self, value: bool) -> None:
    """Set instance as mandatory or not.

    Mandatory instance can't be disabled in UI.

    Args:
        value (bool): True if instance should be mandatory, False
            otherwise.

    """
    if value is self._is_mandatory:
        return
    self._is_mandatory = value
    if value is True:
        self["active"] = True
    self._create_context.instance_requirement_changed(self.id)

set_parent(instance_id, flags)

Set parent instance id and parenting flags.

Parameters:

Name Type Description Default
instance_id Optional[str]

Parent instance id.

required
flags int

Parenting flags.

required
Source code in client/ayon_core/pipeline/create/structures.py
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
def set_parent(
    self, instance_id: Optional[str], flags: int
) -> None:
    """Set parent instance id and parenting flags.

    Args:
        instance_id (Optional[str]): Parent instance id.
        flags (int): Parenting flags.

    """
    changed = False
    if instance_id != self._parent_instance_id:
        changed = True
        self._parent_instance_id = instance_id

    if flags is None:
        flags = 0

    if self._parent_flags != flags:
        self._parent_flags = flags
        changed = True

    if changed:
        self._create_context.instance_parent_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
970
971
972
973
974
975
976
977
978
979
980
981
982
983
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
270
271
272
273
274
275
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
        })

IntEnum

Bases: int, Enum

An int-based Enum class that allows for int comparison.

Source code in client/ayon_core/pipeline/create/structures.py
26
27
28
29
30
class IntEnum(int, Enum):
    """An int-based Enum class that allows for int comparison."""

    def __int__(self) -> int:
        return self.value

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
278
279
280
281
282
283
284
285
286
287
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
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
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 __setitem__(self, key, value):
        """Set value for plugin.

        Args:
            key (str): Plugin name.
            value (dict[str, Any]): Value to set.

        """
        current_value = self._data.get(key)
        if isinstance(current_value, PublishAttributeValues):
            current_value.set_value(value)
        else:
            self._data[key] = value

    def __delitem__(self, key):
        self.pop(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

__setitem__(key, value)

Set value for plugin.

Parameters:

Name Type Description Default
key str

Plugin name.

required
value dict[str, Any]

Value to set.

required
Source code in client/ayon_core/pipeline/create/structures.py
311
312
313
314
315
316
317
318
319
320
321
322
323
def __setitem__(self, key, value):
    """Set value for plugin.

    Args:
        key (str): Plugin name.
        value (dict[str, Any]): Value to set.

    """
    current_value = self._data.get(key)
    if isinstance(current_value, PublishAttributeValues):
        current_value.set_value(value)
    else:
        self._data[key] = value

data_to_store()

Convert attribute values to "data to store".

Source code in client/ayon_core/pipeline/create/structures.py
375
376
377
378
379
380
381
382
383
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
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
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
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
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
    )