Skip to content

pipeline

AYONPyblishPluginMixin

Source code in client/ayon_core/pipeline/publish/publish_plugins.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
class AYONPyblishPluginMixin:
    # TODO
    # executable_in_thread = False
    #
    # state_message = None
    # state_percent = None
    # _state_change_callbacks = []
    #
    # def set_state(self, percent=None, message=None):
    #     """Inner callback of plugin that would help to show in UI state.
    #
    #     Plugin have registered callbacks on state change which could trigger
    #     update message and percent in UI and repaint the change.
    #
    #     This part must be optional and should not be used to display errors
    #     or for logging.
    #
    #     Message should be short without details.
    #
    #     Args:
    #         percent(int): Percent of processing in range <1-100>.
    #         message(str): Message which will be shown to user (if in UI).
    #     """
    #     if percent is not None:
    #         self.state_percent = percent
    #
    #     if message:
    #         self.state_message = message
    #
    #     for callback in self._state_change_callbacks:
    #         callback(self)

    @classmethod
    def register_create_context_callbacks(
        cls, create_context: "CreateContext"
    ):
        """Register callbacks for create context.

        It is possible to register callbacks listening to changes happened
        in create context.

        Methods available on create context:
        - add_instances_added_callback
        - add_instances_removed_callback
        - add_value_changed_callback
        - add_pre_create_attr_defs_change_callback
        - add_create_attr_defs_change_callback
        - add_publish_attr_defs_change_callback

        Args:
            create_context (CreateContext): Create context.

        """
        pass

    @classmethod
    def get_attribute_defs(cls):
        """Publish attribute definitions.

        Attributes available for all families in plugin's `families` attribute.

        Returns:
            list[AbstractAttrDef]: Attribute definitions for plugin.

        """
        return []

    @classmethod
    def get_attr_defs_for_context(cls, create_context: "CreateContext"):
        """Publish attribute definitions for context.

        Attributes available for all families in plugin's `families` attribute.

        Args:
            create_context (CreateContext): Create context.

        Returns:
            list[AbstractAttrDef]: Attribute definitions for plugin.

        """
        if cls.__instanceEnabled__:
            return []
        return cls.get_attribute_defs()

    @classmethod
    def instance_matches_plugin_families(
        cls, instance: Optional["CreatedInstance"]
    ):
        """Check if instance matches families.

        Args:
            instance (Optional[CreatedInstance]): Instance to check. Or None
                for context.

        Returns:
            bool: True if instance matches plugin families.

        """
        if instance is None:
            return not cls.__instanceEnabled__

        if not cls.__instanceEnabled__:
            return False

        families = [instance.product_type]
        families.extend(instance.get("families", []))
        for _ in pyblish.logic.plugins_by_families([cls], families):
            return True
        return False

    @classmethod
    def get_attr_defs_for_instance(
        cls, create_context: "CreateContext", instance: "CreatedInstance"
    ):
        """Publish attribute definitions for an instance.

        Attributes available for all families in plugin's `families` attribute.

        Args:
            create_context (CreateContext): Create context.
            instance (CreatedInstance): Instance for which attributes are
                collected.

        Returns:
            list[AbstractAttrDef]: Attribute definitions for plugin.

        """
        if not cls.instance_matches_plugin_families(instance):
            return []
        return cls.get_attribute_defs()

    @classmethod
    def convert_attribute_values(
        cls, create_context: "CreateContext", instance: "CreatedInstance"
    ):
        """Convert attribute values for instance.

        Args:
            create_context (CreateContext): Create context.
            instance (CreatedInstance): Instance for which attributes are
                converted.

        """
        return

    @staticmethod
    def get_attr_values_from_data_for_plugin(plugin, data):
        """Get attribute values for attribute definitions from data.

        Args:
            plugin (Union[publish.api.Plugin, Type[publish.api.Plugin]]): The
                plugin for which attributes are extracted.
            data(dict): Data from instance or context.
        """

        if not inspect.isclass(plugin):
            plugin = plugin.__class__

        return (
            data
            .get("publish_attributes", {})
            .get(plugin.__name__, {})
        )

    def get_attr_values_from_data(self, data):
        """Get attribute values for attribute definitions from data.

        Args:
            data(dict): Data from instance or context.
        """

        return self.get_attr_values_from_data_for_plugin(self.__class__, data)

convert_attribute_values(create_context, instance) classmethod

Convert attribute values for instance.

Parameters:

Name Type Description Default
create_context CreateContext

Create context.

required
instance CreatedInstance

Instance for which attributes are converted.

required
Source code in client/ayon_core/pipeline/publish/publish_plugins.py
235
236
237
238
239
240
241
242
243
244
245
246
247
@classmethod
def convert_attribute_values(
    cls, create_context: "CreateContext", instance: "CreatedInstance"
):
    """Convert attribute values for instance.

    Args:
        create_context (CreateContext): Create context.
        instance (CreatedInstance): Instance for which attributes are
            converted.

    """
    return

get_attr_defs_for_context(create_context) classmethod

Publish attribute definitions for context.

Attributes available for all families in plugin's families attribute.

Parameters:

Name Type Description Default
create_context CreateContext

Create context.

required

Returns:

Type Description

list[AbstractAttrDef]: Attribute definitions for plugin.

Source code in client/ayon_core/pipeline/publish/publish_plugins.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
@classmethod
def get_attr_defs_for_context(cls, create_context: "CreateContext"):
    """Publish attribute definitions for context.

    Attributes available for all families in plugin's `families` attribute.

    Args:
        create_context (CreateContext): Create context.

    Returns:
        list[AbstractAttrDef]: Attribute definitions for plugin.

    """
    if cls.__instanceEnabled__:
        return []
    return cls.get_attribute_defs()

get_attr_defs_for_instance(create_context, instance) classmethod

Publish attribute definitions for an instance.

Attributes available for all families in plugin's families attribute.

Parameters:

Name Type Description Default
create_context CreateContext

Create context.

required
instance CreatedInstance

Instance for which attributes are collected.

required

Returns:

Type Description

list[AbstractAttrDef]: Attribute definitions for plugin.

Source code in client/ayon_core/pipeline/publish/publish_plugins.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
@classmethod
def get_attr_defs_for_instance(
    cls, create_context: "CreateContext", instance: "CreatedInstance"
):
    """Publish attribute definitions for an instance.

    Attributes available for all families in plugin's `families` attribute.

    Args:
        create_context (CreateContext): Create context.
        instance (CreatedInstance): Instance for which attributes are
            collected.

    Returns:
        list[AbstractAttrDef]: Attribute definitions for plugin.

    """
    if not cls.instance_matches_plugin_families(instance):
        return []
    return cls.get_attribute_defs()

get_attr_values_from_data(data)

Get attribute values for attribute definitions from data.

Parameters:

Name Type Description Default
data(dict)

Data from instance or context.

required
Source code in client/ayon_core/pipeline/publish/publish_plugins.py
268
269
270
271
272
273
274
275
def get_attr_values_from_data(self, data):
    """Get attribute values for attribute definitions from data.

    Args:
        data(dict): Data from instance or context.
    """

    return self.get_attr_values_from_data_for_plugin(self.__class__, data)

get_attr_values_from_data_for_plugin(plugin, data) staticmethod

Get attribute values for attribute definitions from data.

Parameters:

Name Type Description Default
plugin Union[Plugin, Type[Plugin]]

The plugin for which attributes are extracted.

required
data(dict)

Data from instance or context.

required
Source code in client/ayon_core/pipeline/publish/publish_plugins.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@staticmethod
def get_attr_values_from_data_for_plugin(plugin, data):
    """Get attribute values for attribute definitions from data.

    Args:
        plugin (Union[publish.api.Plugin, Type[publish.api.Plugin]]): The
            plugin for which attributes are extracted.
        data(dict): Data from instance or context.
    """

    if not inspect.isclass(plugin):
        plugin = plugin.__class__

    return (
        data
        .get("publish_attributes", {})
        .get(plugin.__name__, {})
    )

get_attribute_defs() classmethod

Publish attribute definitions.

Attributes available for all families in plugin's families attribute.

Returns:

Type Description

list[AbstractAttrDef]: Attribute definitions for plugin.

Source code in client/ayon_core/pipeline/publish/publish_plugins.py
159
160
161
162
163
164
165
166
167
168
169
@classmethod
def get_attribute_defs(cls):
    """Publish attribute definitions.

    Attributes available for all families in plugin's `families` attribute.

    Returns:
        list[AbstractAttrDef]: Attribute definitions for plugin.

    """
    return []

instance_matches_plugin_families(instance) classmethod

Check if instance matches families.

Parameters:

Name Type Description Default
instance Optional[CreatedInstance]

Instance to check. Or None for context.

required

Returns:

Name Type Description
bool

True if instance matches plugin families.

Source code in client/ayon_core/pipeline/publish/publish_plugins.py
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
@classmethod
def instance_matches_plugin_families(
    cls, instance: Optional["CreatedInstance"]
):
    """Check if instance matches families.

    Args:
        instance (Optional[CreatedInstance]): Instance to check. Or None
            for context.

    Returns:
        bool: True if instance matches plugin families.

    """
    if instance is None:
        return not cls.__instanceEnabled__

    if not cls.__instanceEnabled__:
        return False

    families = [instance.product_type]
    families.extend(instance.get("families", []))
    for _ in pyblish.logic.plugins_by_families([cls], families):
        return True
    return False

register_create_context_callbacks(create_context) classmethod

Register callbacks for create context.

It is possible to register callbacks listening to changes happened in create context.

Methods available on create context: - add_instances_added_callback - add_instances_removed_callback - add_value_changed_callback - add_pre_create_attr_defs_change_callback - add_create_attr_defs_change_callback - add_publish_attr_defs_change_callback

Parameters:

Name Type Description Default
create_context CreateContext

Create context.

required
Source code in client/ayon_core/pipeline/publish/publish_plugins.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
@classmethod
def register_create_context_callbacks(
    cls, create_context: "CreateContext"
):
    """Register callbacks for create context.

    It is possible to register callbacks listening to changes happened
    in create context.

    Methods available on create context:
    - add_instances_added_callback
    - add_instances_removed_callback
    - add_value_changed_callback
    - add_pre_create_attr_defs_change_callback
    - add_create_attr_defs_change_callback
    - add_publish_attr_defs_change_callback

    Args:
        create_context (CreateContext): Create context.

    """
    pass

Anatomy

Bases: BaseAnatomy

Source code in client/ayon_core/pipeline/anatomy/anatomy.py
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
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
class Anatomy(BaseAnatomy):
    _project_cache = NestedCacheItem(lifetime=10)
    _sitesync_addon_cache = CacheItem(lifetime=60)
    _default_site_id_cache = NestedCacheItem(lifetime=60)
    _root_overrides_cache = NestedCacheItem(2, lifetime=60)

    def __init__(
        self, project_name=None, site_name=None, project_entity=None
    ):
        if not project_name:
            project_name = os.environ.get("AYON_PROJECT_NAME")

        if not project_name:
            raise ProjectNotSet((
                "Implementation bug: Project name is not set. Anatomy requires"
                " to load data for specific project."
            ))

        if not project_entity:
            project_entity = self.get_project_entity_from_cache(project_name)
        root_overrides = self._get_site_root_overrides(
            project_name, site_name
        )

        super(Anatomy, self).__init__(project_entity, root_overrides)

    @classmethod
    def get_project_entity_from_cache(cls, project_name):
        project_cache = cls._project_cache[project_name]
        if not project_cache.is_valid:
            project_cache.update_data(ayon_api.get_project(project_name))
        return copy.deepcopy(project_cache.get_data())

    @classmethod
    def get_sitesync_addon(cls):
        if not cls._sitesync_addon_cache.is_valid:
            manager = AddonsManager()
            cls._sitesync_addon_cache.update_data(
                manager.get_enabled_addon("sitesync")
            )
        return cls._sitesync_addon_cache.get_data()

    @classmethod
    def _get_studio_roots_overrides(cls, project_name):
        """This would return 'studio' site override by local settings.

        Notes:
            This logic handles local overrides of studio site which may be
                available even when sync server is not enabled.
            Handling of 'studio' and 'local' site was separated as preparation
                for AYON development where that will be received from
                separated sources.

        Args:
            project_name (str): Name of project.

        Returns:
            Union[Dict[str, str], None]): Local root overrides.
        """
        if not project_name:
            return
        return ayon_api.get_project_roots_for_site(
            project_name, get_local_site_id()
        )

    @classmethod
    def _get_site_root_overrides(cls, project_name, site_name):
        """Get root overrides for site.

        Args:
            project_name (str): Project name for which root overrides should be
                received.
            site_name (Union[str, None]): Name of site for which root overrides
                should be returned.
        """

        # First check if sync server is available and enabled
        sitesync_addon = cls.get_sitesync_addon()
        if sitesync_addon is None or not sitesync_addon.enabled:
            # QUESTION is ok to force 'studio' when site sync is not enabled?
            site_name = "studio"

        elif not site_name:
            # Use sync server to receive active site name
            project_cache = cls._default_site_id_cache[project_name]
            if not project_cache.is_valid:
                project_cache.update_data(
                    sitesync_addon.get_active_site_type(project_name)
                )
            site_name = project_cache.get_data()

        site_cache = cls._root_overrides_cache[project_name][site_name]
        if not site_cache.is_valid:
            if site_name == "studio":
                # Handle studio root overrides without sync server
                # - studio root overrides can be done even without sync server
                roots_overrides = cls._get_studio_roots_overrides(
                    project_name
                )
            else:
                # Ask sync server to get roots overrides
                roots_overrides = sitesync_addon.get_site_root_overrides(
                    project_name, site_name
                )
            site_cache.update_data(roots_overrides)
        return site_cache.get_data()

AutoCreator

Bases: BaseCreator

Creator which is automatically triggered without user interaction.

Can be used e.g. for workfile.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
959
960
961
962
963
964
965
966
967
class AutoCreator(BaseCreator):
    """Creator which is automatically triggered without user interaction.

    Can be used e.g. for `workfile`.
    """

    def remove_instances(self, instances):
        """Skip removal."""
        pass

remove_instances(instances)

Skip removal.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
965
966
967
def remove_instances(self, instances):
    """Skip removal."""
    pass

BaseCreator

Bases: ABC

Plugin that create and modify instance data before publishing process.

We should maybe find better name as creation is only one part of its logic and to avoid expectations that it is the same as avalon.api.Creator.

Single object should be used for multiple instances instead of single instance per one creator object. Do not store temp data or mid-process data to self if it's not Plugin specific.

Parameters:

Name Type Description Default
project_settings dict[str, Any]

Project settings.

required
create_context CreateContext

Context which initialized creator.

required
headless bool

Running in headless mode.

False
Source code in client/ayon_core/pipeline/create/creator_plugins.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
class BaseCreator(ABC):
    """Plugin that create and modify instance data before publishing process.

    We should maybe find better name as creation is only one part of its logic
    and to avoid expectations that it is the same as `avalon.api.Creator`.

    Single object should be used for multiple instances instead of single
    instance per one creator object. Do not store temp data or mid-process data
    to `self` if it's not Plugin specific.

    Args:
        project_settings (dict[str, Any]): Project settings.
        create_context (CreateContext): Context which initialized creator.
        headless (bool): Running in headless mode.
    """

    # Label shown in UI
    label = None
    group_label = None
    # Cached group label after first call 'get_group_label'
    _cached_group_label = None

    # Order in which will be plugin executed (collect & update instances)
    #   less == earlier -> Order '90' will be processed before '100'
    order = 100

    # Variable to store logger
    _log = None

    # Creator is enabled (Probably does not have reason of existence?)
    enabled = True

    # Creator (and product type) icon
    # - may not be used if `get_icon` is reimplemented
    icon = None

    # Instance attribute definitions that can be changed per instance
    # - returns list of attribute definitions from
    #       `ayon_core.lib.attribute_definitions`
    instance_attr_defs: "list[AbstractAttrDef]" = []

    # Filtering by host name - can be used to be filtered by host name
    # - used on all hosts when set to 'None' for Backwards compatibility
    #   - was added afterwards
    # QUESTION make this required?
    host_name: Optional[str] = None

    # Settings auto-apply helpers
    # Root key in project settings (mandatory for auto-apply to work)
    settings_category: Optional[str] = None
    # Name of plugin in create settings > class name is used if not set
    settings_name: Optional[str] = None

    def __init__(
        self, project_settings, create_context, headless=False
    ):
        # Reference to CreateContext
        self.create_context = create_context
        self.project_settings = project_settings

        # Creator is running in headless mode (without UI elements)
        # - we may use UI inside processing this attribute should be checked
        self.headless = headless

        self.apply_settings(project_settings)
        self.register_callbacks()

    @staticmethod
    def _get_settings_values(project_settings, category_name, plugin_name):
        """Helper method to get settings values.

        Args:
            project_settings (dict[str, Any]): Project settings.
            category_name (str): Category of settings.
            plugin_name (str): Name of settings.

        Returns:
            Optional[dict[str, Any]]: Settings values or None.
        """

        settings = project_settings.get(category_name)
        if not settings:
            return None

        create_settings = settings.get("create")
        if not create_settings:
            return None

        return create_settings.get(plugin_name)

    def apply_settings(self, project_settings):
        """Method called on initialization of plugin to apply settings.

        Default implementation tries to auto-apply settings values if are
            in expected hierarchy.

        Data hierarchy to auto-apply settings:
            ├─ {self.settings_category}                 - Root key in settings
            │ └─ "create"                               - Hardcoded key
            │   └─ {self.settings_name} | {class name}  - Name of plugin
            │     ├─ ... attribute values...            - Attribute/value pair

        It is mandatory to define 'settings_category' attribute. Attribute
        'settings_name' is optional and class name is used if is not defined.

        Example data:
            ProjectSettings {
                "maya": {                    # self.settings_category
                    "create": {              # Hardcoded key
                        "CreateAnimation": { # self.settings_name / class name
                            "enabled": True, # --- Attributes to set ---
                            "optional": True,#
                            "active": True,  #
                            "fps": 25,       # -------------------------
                        },
                        ...
                    },
                    ...
                },
                ...
            }

        Args:
            project_settings (dict[str, Any]): Project settings.
        """

        settings_category = self.settings_category
        if not settings_category:
            return

        cls_name = self.__class__.__name__
        settings_name = self.settings_name or cls_name

        settings = self._get_settings_values(
            project_settings, settings_category, settings_name
        )
        if settings is None:
            self.log.debug("No settings found for {}".format(cls_name))
            return

        for key, value in settings.items():
            # Log out attributes that are not defined on plugin object
            # - those may be potential dangerous typos in settings
            if not hasattr(self, key):
                self.log.debug((
                    "Applying settings to unknown attribute '{}' on '{}'."
                ).format(
                    key, cls_name
                ))
            setattr(self, key, value)

    def register_callbacks(self):
        """Register callbacks for creator.

        Default implementation does nothing. It can be overridden to register
        callbacks for creator.
        """
        pass

    @property
    def identifier(self):
        """Identifier of creator (must be unique).

        Default implementation returns plugin's product type.
        """

        return self.product_type

    @property
    @abstractmethod
    def product_type(self):
        """Family that plugin represents."""

        pass

    @property
    def project_name(self):
        """Current project name.

        Returns:
            str: Name of a project.
        """

        return self.create_context.project_name

    @property
    def project_anatomy(self):
        """Current project anatomy.

        Returns:
            Anatomy: Project anatomy object.
        """

        return self.create_context.project_anatomy

    @property
    def host(self):
        return self.create_context.host

    def get_group_label(self):
        """Group label under which are instances grouped in UI.

        Default implementation use attributes in this order:
            - 'group_label' -> 'label' -> 'identifier'
                Keep in mind that 'identifier' use 'product_type' by default.

        Returns:
            str: Group label that can be used for grouping of instances in UI.
                Group label can be overridden by instance itself.
        """

        if self._cached_group_label is None:
            label = self.identifier
            if self.group_label:
                label = self.group_label
            elif self.label:
                label = self.label
            self._cached_group_label = label
        return self._cached_group_label

    @property
    def log(self):
        """Logger of the plugin.

        Returns:
            logging.Logger: Logger with name of the plugin.
        """

        if self._log is None:
            self._log = Logger.get_logger(self.__class__.__name__)
        return self._log

    def _create_instance(
        self,
        product_name: str,
        data: Dict[str, Any],
        product_type: Optional[str] = None
    ) -> CreatedInstance:
        """Create instance and add instance to context.

        Args:
            product_name (str): Product name.
            data (Dict[str, Any]): Instance data.
            product_type (Optional[str]): Product type, object attribute
                'product_type' is used if not passed.

        Returns:
            CreatedInstance: Created instance.

        """
        if product_type is None:
            product_type = self.product_type
        instance = CreatedInstance(
            product_type,
            product_name,
            data,
            creator=self,
        )
        self._add_instance_to_context(instance)
        return instance

    def _add_instance_to_context(self, instance):
        """Helper method to add instance to create context.

        Instances should be stored to DCC workfile metadata to be able reload
        them and also stored to CreateContext in which is creator plugin
        existing at the moment to be able use it without refresh of
        CreateContext.

        Args:
            instance (CreatedInstance): New created instance.
        """

        self.create_context.creator_adds_instance(instance)

    def _remove_instance_from_context(self, instance):
        """Helper method to remove instance from create context.

        Instances must be removed from DCC workfile metadat aand from create
        context in which plugin is existing at the moment of removal to
        propagate the change without restarting create context.

        Args:
            instance (CreatedInstance): Instance which should be removed.
        """

        self.create_context.creator_removed_instance(instance)

    @abstractmethod
    def create(self):
        """Create new instance.

        Replacement of `process` method from avalon implementation.
        - must expect all data that were passed to init in previous
            implementation
        """

        pass

    @abstractmethod
    def collect_instances(self):
        """Collect existing instances related to this creator plugin.

        The implementation differs on host abilities. The creator has to
        collect metadata about instance and create 'CreatedInstance' object
        which should be added to 'CreateContext'.

        Example:
        ```python
        def collect_instances(self):
            # Getting existing instances is different per host implementation
            for instance_data in pipeline.list_instances():
                # Process only instances that were created by this creator
                creator_id = instance_data.get("creator_identifier")
                if creator_id == self.identifier:
                    # Create instance object from existing data
                    instance = CreatedInstance.from_existing(
                        instance_data, self
                    )
                    # Add instance to create context
                    self._add_instance_to_context(instance)
        ```
        """

        pass

    @abstractmethod
    def update_instances(self, update_list):
        """Store changes of existing instances so they can be recollected.

        Args:
            update_list (list[UpdateData]): Gets list of tuples. Each item
                contain changed instance and it's changes.
        """

        pass

    @abstractmethod
    def remove_instances(self, instances):
        """Method called on instance removal.

        Can also remove instance metadata from context but should return
        'True' if did so.

        Args:
            instances (list[CreatedInstance]): Instance objects which should be
                removed.
        """

        pass

    def get_icon(self):
        """Icon of creator (product type).

        Can return path to image file or awesome icon name.
        """

        return self.icon

    def get_dynamic_data(
        self,
        project_name,
        folder_entity,
        task_entity,
        variant,
        host_name,
        instance
    ):
        """Dynamic data for product name filling.

        These may be dynamically created based on current context of workfile.
        """

        return {}

    def get_product_name(
        self,
        project_name,
        folder_entity,
        task_entity,
        variant,
        host_name=None,
        instance=None,
        project_entity=None,
    ):
        """Return product name for passed context.

        Method is also called on product name update. In that case origin
        instance is passed in.

        Args:
            project_name (str): Project name.
            folder_entity (dict): Folder entity.
            task_entity (dict): Task entity.
            variant (str): Product name variant. In most of cases user input.
            host_name (Optional[str]): Which host creates product. Defaults
                to host name on create context.
            instance (Optional[CreatedInstance]): Object of 'CreatedInstance'
                for which is product name updated. Passed only on product name
                update.
            project_entity (Optional[dict[str, Any]]): Project entity.

        """
        if host_name is None:
            host_name = self.create_context.host_name

        task_name = task_type = None
        if task_entity:
            task_name = task_entity["name"]
            task_type = task_entity["taskType"]

        dynamic_data = self.get_dynamic_data(
            project_name,
            folder_entity,
            task_entity,
            variant,
            host_name,
            instance
        )

        cur_project_name = self.create_context.get_current_project_name()
        if not project_entity and project_name == cur_project_name:
            project_entity = self.create_context.get_current_project_entity()

        return get_product_name(
            project_name,
            task_name,
            task_type,
            host_name,
            self.product_type,
            variant,
            dynamic_data=dynamic_data,
            project_settings=self.project_settings,
            project_entity=project_entity,
        )

    def get_instance_attr_defs(self):
        """Plugin attribute definitions.

        Attribute definitions of plugin that hold data about created instance
        and values are stored to metadata for future usage and for publishing
        purposes.

        NOTE:
        Convert method should be implemented which should care about updating
        keys/values when plugin attributes change.

        Returns:
            list[AbstractAttrDef]: Attribute definitions that can be tweaked
                for created instance.
        """

        return self.instance_attr_defs

    def get_attr_defs_for_instance(self, instance):
        """Get attribute definitions for an instance.

        Args:
            instance (CreatedInstance): Instance for which to get
                attribute definitions.

        """
        return self.get_instance_attr_defs()

    @property
    def collection_shared_data(self):
        """Access to shared data that can be used during creator's collection.

        Returns:
            dict[str, Any]: Shared data.

        Raises:
            UnavailableSharedData: When called out of collection phase.
        """

        return self.create_context.collection_shared_data

    def set_instance_thumbnail_path(self, instance_id, thumbnail_path=None):
        """Set path to thumbnail for instance."""

        self.create_context.thumbnail_paths_by_instance_id[instance_id] = (
            thumbnail_path
        )

    def get_next_versions_for_instances(self, instances):
        """Prepare next versions for instances.

        This is helper method to receive next possible versions for instances.
        It is using context information on instance to receive them,
        'folderPath' and 'product'.

        Output will contain version by each instance id.

        Args:
            instances (list[CreatedInstance]): Instances for which to get next
                versions.

        Returns:
            dict[str, int]: Next versions by instance id.
        """

        return get_next_versions_for_instances(
            self.create_context.project_name, instances
        )

collection_shared_data property

Access to shared data that can be used during creator's collection.

Returns:

Type Description

dict[str, Any]: Shared data.

Raises:

Type Description
UnavailableSharedData

When called out of collection phase.

identifier property

Identifier of creator (must be unique).

Default implementation returns plugin's product type.

log property

Logger of the plugin.

Returns:

Type Description

logging.Logger: Logger with name of the plugin.

product_type abstractmethod property

Family that plugin represents.

project_anatomy property

Current project anatomy.

Returns:

Name Type Description
Anatomy

Project anatomy object.

project_name property

Current project name.

Returns:

Name Type Description
str

Name of a project.

apply_settings(project_settings)

Method called on initialization of plugin to apply settings.

Default implementation tries to auto-apply settings values if are in expected hierarchy.

Data hierarchy to auto-apply settings

├─ {self.settings_category} - Root key in settings │ └─ "create" - Hardcoded key │ └─ {self.settings_name} | {class name} - Name of plugin │ ├─ ... attribute values... - Attribute/value pair

It is mandatory to define 'settings_category' attribute. Attribute 'settings_name' is optional and class name is used if is not defined.

Example data

ProjectSettings { "maya": { # self.settings_category "create": { # Hardcoded key "CreateAnimation": { # self.settings_name / class name "enabled": True, # --- Attributes to set --- "optional": True,# "active": True, # "fps": 25, # ------------------------- }, ... }, ... }, ... }

Parameters:

Name Type Description Default
project_settings dict[str, Any]

Project settings.

required
Source code in client/ayon_core/pipeline/create/creator_plugins.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
def apply_settings(self, project_settings):
    """Method called on initialization of plugin to apply settings.

    Default implementation tries to auto-apply settings values if are
        in expected hierarchy.

    Data hierarchy to auto-apply settings:
        ├─ {self.settings_category}                 - Root key in settings
        │ └─ "create"                               - Hardcoded key
        │   └─ {self.settings_name} | {class name}  - Name of plugin
        │     ├─ ... attribute values...            - Attribute/value pair

    It is mandatory to define 'settings_category' attribute. Attribute
    'settings_name' is optional and class name is used if is not defined.

    Example data:
        ProjectSettings {
            "maya": {                    # self.settings_category
                "create": {              # Hardcoded key
                    "CreateAnimation": { # self.settings_name / class name
                        "enabled": True, # --- Attributes to set ---
                        "optional": True,#
                        "active": True,  #
                        "fps": 25,       # -------------------------
                    },
                    ...
                },
                ...
            },
            ...
        }

    Args:
        project_settings (dict[str, Any]): Project settings.
    """

    settings_category = self.settings_category
    if not settings_category:
        return

    cls_name = self.__class__.__name__
    settings_name = self.settings_name or cls_name

    settings = self._get_settings_values(
        project_settings, settings_category, settings_name
    )
    if settings is None:
        self.log.debug("No settings found for {}".format(cls_name))
        return

    for key, value in settings.items():
        # Log out attributes that are not defined on plugin object
        # - those may be potential dangerous typos in settings
        if not hasattr(self, key):
            self.log.debug((
                "Applying settings to unknown attribute '{}' on '{}'."
            ).format(
                key, cls_name
            ))
        setattr(self, key, value)

collect_instances() abstractmethod

Collect existing instances related to this creator plugin.

The implementation differs on host abilities. The creator has to collect metadata about instance and create 'CreatedInstance' object which should be added to 'CreateContext'.

Example:

def collect_instances(self):
    # Getting existing instances is different per host implementation
    for instance_data in pipeline.list_instances():
        # Process only instances that were created by this creator
        creator_id = instance_data.get("creator_identifier")
        if creator_id == self.identifier:
            # Create instance object from existing data
            instance = CreatedInstance.from_existing(
                instance_data, self
            )
            # Add instance to create context
            self._add_instance_to_context(instance)
Source code in client/ayon_core/pipeline/create/creator_plugins.py
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
@abstractmethod
def collect_instances(self):
    """Collect existing instances related to this creator plugin.

    The implementation differs on host abilities. The creator has to
    collect metadata about instance and create 'CreatedInstance' object
    which should be added to 'CreateContext'.

    Example:
    ```python
    def collect_instances(self):
        # Getting existing instances is different per host implementation
        for instance_data in pipeline.list_instances():
            # Process only instances that were created by this creator
            creator_id = instance_data.get("creator_identifier")
            if creator_id == self.identifier:
                # Create instance object from existing data
                instance = CreatedInstance.from_existing(
                    instance_data, self
                )
                # Add instance to create context
                self._add_instance_to_context(instance)
    ```
    """

    pass

create() abstractmethod

Create new instance.

Replacement of process method from avalon implementation. - must expect all data that were passed to init in previous implementation

Source code in client/ayon_core/pipeline/create/creator_plugins.py
433
434
435
436
437
438
439
440
441
442
@abstractmethod
def create(self):
    """Create new instance.

    Replacement of `process` method from avalon implementation.
    - must expect all data that were passed to init in previous
        implementation
    """

    pass

get_attr_defs_for_instance(instance)

Get attribute definitions for an instance.

Parameters:

Name Type Description Default
instance CreatedInstance

Instance for which to get attribute definitions.

required
Source code in client/ayon_core/pipeline/create/creator_plugins.py
599
600
601
602
603
604
605
606
607
def get_attr_defs_for_instance(self, instance):
    """Get attribute definitions for an instance.

    Args:
        instance (CreatedInstance): Instance for which to get
            attribute definitions.

    """
    return self.get_instance_attr_defs()

get_dynamic_data(project_name, folder_entity, task_entity, variant, host_name, instance)

Dynamic data for product name filling.

These may be dynamically created based on current context of workfile.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
def get_dynamic_data(
    self,
    project_name,
    folder_entity,
    task_entity,
    variant,
    host_name,
    instance
):
    """Dynamic data for product name filling.

    These may be dynamically created based on current context of workfile.
    """

    return {}

get_group_label()

Group label under which are instances grouped in UI.

Default implementation use attributes in this order
  • 'group_label' -> 'label' -> 'identifier' Keep in mind that 'identifier' use 'product_type' by default.

Returns:

Name Type Description
str

Group label that can be used for grouping of instances in UI. Group label can be overridden by instance itself.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
def get_group_label(self):
    """Group label under which are instances grouped in UI.

    Default implementation use attributes in this order:
        - 'group_label' -> 'label' -> 'identifier'
            Keep in mind that 'identifier' use 'product_type' by default.

    Returns:
        str: Group label that can be used for grouping of instances in UI.
            Group label can be overridden by instance itself.
    """

    if self._cached_group_label is None:
        label = self.identifier
        if self.group_label:
            label = self.group_label
        elif self.label:
            label = self.label
        self._cached_group_label = label
    return self._cached_group_label

get_icon()

Icon of creator (product type).

Can return path to image file or awesome icon name.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
496
497
498
499
500
501
502
def get_icon(self):
    """Icon of creator (product type).

    Can return path to image file or awesome icon name.
    """

    return self.icon

get_instance_attr_defs()

Plugin attribute definitions.

Attribute definitions of plugin that hold data about created instance and values are stored to metadata for future usage and for publishing purposes.

NOTE: Convert method should be implemented which should care about updating keys/values when plugin attributes change.

Returns:

Type Description

list[AbstractAttrDef]: Attribute definitions that can be tweaked for created instance.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
def get_instance_attr_defs(self):
    """Plugin attribute definitions.

    Attribute definitions of plugin that hold data about created instance
    and values are stored to metadata for future usage and for publishing
    purposes.

    NOTE:
    Convert method should be implemented which should care about updating
    keys/values when plugin attributes change.

    Returns:
        list[AbstractAttrDef]: Attribute definitions that can be tweaked
            for created instance.
    """

    return self.instance_attr_defs

get_next_versions_for_instances(instances)

Prepare next versions for instances.

This is helper method to receive next possible versions for instances. It is using context information on instance to receive them, 'folderPath' and 'product'.

Output will contain version by each instance id.

Parameters:

Name Type Description Default
instances list[CreatedInstance]

Instances for which to get next versions.

required

Returns:

Type Description

dict[str, int]: Next versions by instance id.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
def get_next_versions_for_instances(self, instances):
    """Prepare next versions for instances.

    This is helper method to receive next possible versions for instances.
    It is using context information on instance to receive them,
    'folderPath' and 'product'.

    Output will contain version by each instance id.

    Args:
        instances (list[CreatedInstance]): Instances for which to get next
            versions.

    Returns:
        dict[str, int]: Next versions by instance id.
    """

    return get_next_versions_for_instances(
        self.create_context.project_name, instances
    )

get_product_name(project_name, folder_entity, task_entity, variant, host_name=None, instance=None, project_entity=None)

Return product name for passed context.

Method is also called on product name update. In that case origin instance is passed in.

Parameters:

Name Type Description Default
project_name str

Project name.

required
folder_entity dict

Folder entity.

required
task_entity dict

Task entity.

required
variant str

Product name variant. In most of cases user input.

required
host_name Optional[str]

Which host creates product. Defaults to host name on create context.

None
instance Optional[CreatedInstance]

Object of 'CreatedInstance' for which is product name updated. Passed only on product name update.

None
project_entity Optional[dict[str, Any]]

Project entity.

None
Source code in client/ayon_core/pipeline/create/creator_plugins.py
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
def get_product_name(
    self,
    project_name,
    folder_entity,
    task_entity,
    variant,
    host_name=None,
    instance=None,
    project_entity=None,
):
    """Return product name for passed context.

    Method is also called on product name update. In that case origin
    instance is passed in.

    Args:
        project_name (str): Project name.
        folder_entity (dict): Folder entity.
        task_entity (dict): Task entity.
        variant (str): Product name variant. In most of cases user input.
        host_name (Optional[str]): Which host creates product. Defaults
            to host name on create context.
        instance (Optional[CreatedInstance]): Object of 'CreatedInstance'
            for which is product name updated. Passed only on product name
            update.
        project_entity (Optional[dict[str, Any]]): Project entity.

    """
    if host_name is None:
        host_name = self.create_context.host_name

    task_name = task_type = None
    if task_entity:
        task_name = task_entity["name"]
        task_type = task_entity["taskType"]

    dynamic_data = self.get_dynamic_data(
        project_name,
        folder_entity,
        task_entity,
        variant,
        host_name,
        instance
    )

    cur_project_name = self.create_context.get_current_project_name()
    if not project_entity and project_name == cur_project_name:
        project_entity = self.create_context.get_current_project_entity()

    return get_product_name(
        project_name,
        task_name,
        task_type,
        host_name,
        self.product_type,
        variant,
        dynamic_data=dynamic_data,
        project_settings=self.project_settings,
        project_entity=project_entity,
    )

register_callbacks()

Register callbacks for creator.

Default implementation does nothing. It can be overridden to register callbacks for creator.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
296
297
298
299
300
301
302
def register_callbacks(self):
    """Register callbacks for creator.

    Default implementation does nothing. It can be overridden to register
    callbacks for creator.
    """
    pass

remove_instances(instances) abstractmethod

Method called on instance removal.

Can also remove instance metadata from context but should return 'True' if did so.

Parameters:

Name Type Description Default
instances list[CreatedInstance]

Instance objects which should be removed.

required
Source code in client/ayon_core/pipeline/create/creator_plugins.py
482
483
484
485
486
487
488
489
490
491
492
493
494
@abstractmethod
def remove_instances(self, instances):
    """Method called on instance removal.

    Can also remove instance metadata from context but should return
    'True' if did so.

    Args:
        instances (list[CreatedInstance]): Instance objects which should be
            removed.
    """

    pass

set_instance_thumbnail_path(instance_id, thumbnail_path=None)

Set path to thumbnail for instance.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
622
623
624
625
626
627
def set_instance_thumbnail_path(self, instance_id, thumbnail_path=None):
    """Set path to thumbnail for instance."""

    self.create_context.thumbnail_paths_by_instance_id[instance_id] = (
        thumbnail_path
    )

update_instances(update_list) abstractmethod

Store changes of existing instances so they can be recollected.

Parameters:

Name Type Description Default
update_list list[UpdateData]

Gets list of tuples. Each item contain changed instance and it's changes.

required
Source code in client/ayon_core/pipeline/create/creator_plugins.py
471
472
473
474
475
476
477
478
479
480
@abstractmethod
def update_instances(self, update_list):
    """Store changes of existing instances so they can be recollected.

    Args:
        update_list (list[UpdateData]): Gets list of tuples. Each item
            contain changed instance and it's changes.
    """

    pass

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
    )

Creator

Bases: BaseCreator

Creator that has more information for artist to show in UI.

Creation requires prepared product name and instance data.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
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
class Creator(BaseCreator):
    """Creator that has more information for artist to show in UI.

    Creation requires prepared product name and instance data.
    """

    # GUI Purposes
    # - default_variants may not be used if `get_default_variants`
    #   is overridden
    default_variants = []

    # Default variant used in 'get_default_variant'
    _default_variant = None

    # Short description of product type
    # - may not be used if `get_description` is overridden
    description = None

    # Detailed description of product type for artists
    # - may not be used if `get_detail_description` is overridden
    detailed_description = None

    # It does make sense to change context on creation
    # - in some cases it may confuse artists because it would not be used
    #      e.g. for buld creators
    create_allow_context_change = True
    # A thumbnail can be passed in precreate attributes
    # - if is set to True is should expect that a thumbnail path under key
    #   PRE_CREATE_THUMBNAIL_KEY can be sent in data with precreate data
    # - is disabled by default because the feature was added in later stages
    #   and creators who would not expect PRE_CREATE_THUMBNAIL_KEY could
    #   cause issues with instance data
    create_allow_thumbnail = False

    # Precreate attribute definitions showed before creation
    # - similar to instance attribute definitions
    pre_create_attr_defs = []

    def __init__(self, *args, **kwargs):
        cls = self.__class__

        # Fix backwards compatibility for plugins which override
        #   'default_variant' attribute directly
        if not isinstance(cls.default_variant, property):
            # Move value from 'default_variant' to '_default_variant'
            self._default_variant = self.default_variant
            # Create property 'default_variant' on the class
            cls.default_variant = property(
                cls._get_default_variant_wrap,
                cls._set_default_variant_wrap
            )
        super().__init__(*args, **kwargs)

    @property
    def show_order(self):
        """Order in which is creator shown in UI.

        Returns:
            int: Order in which is creator shown (less == earlier). By default
                is using Creator's 'order' or processing.
        """

        return self.order

    @abstractmethod
    def create(self, product_name, instance_data, pre_create_data):
        """Create new instance and store it.

        Ideally should be stored to workfile using host implementation.

        Args:
            product_name(str): Product name of created instance.
            instance_data(dict): Base data for instance.
            pre_create_data(dict): Data based on pre creation attributes.
                Those may affect how creator works.
        """

        # instance = CreatedInstance(
        #     self.product_type, product_name, instance_data
        # )
        pass

    def get_description(self):
        """Short description of product type and plugin.

        Returns:
            str: Short description of product type.
        """

        return self.description

    def get_detail_description(self):
        """Description of product type and plugin.

        Can be detailed with markdown or html tags.

        Returns:
            str: Detailed description of product type for artist.
        """

        return self.detailed_description

    def get_default_variants(self):
        """Default variant values for UI tooltips.

        Replacement of `default_variants` attribute. Using method gives
        ability to have some "logic" other than attribute values.

        By default, returns `default_variants` value.

        Returns:
            list[str]: Whisper variants for user input.
        """

        return copy.deepcopy(self.default_variants)

    def get_default_variant(self, only_explicit=False):
        """Default variant value that will be used to prefill variant input.

        This is for user input and value may not be content of result from
        `get_default_variants`.

        Note:
            This method does not allow to have empty string as
                default variant.

        Args:
            only_explicit (Optional[bool]): If True, only explicit default
                variant from '_default_variant' will be returned.

        Returns:
            str: Variant value.
        """

        if only_explicit or self._default_variant:
            return self._default_variant

        for variant in self.get_default_variants():
            return variant
        return DEFAULT_VARIANT_VALUE

    def _get_default_variant_wrap(self):
        """Default variant value that will be used to prefill variant input.

        Wrapper for 'get_default_variant'.

        Notes:
            This method is wrapper for 'get_default_variant'
                for 'default_variant' property, so creator can override
                the method.

        Returns:
            str: Variant value.
        """

        return self.get_default_variant()

    def _set_default_variant_wrap(self, variant):
        """Set default variant value.

        This method is needed for automated settings overrides which are
        changing attributes based on keys in settings.

        Args:
            variant (str): New default variant value.
        """

        self._default_variant = variant

    default_variant = property(
        _get_default_variant_wrap,
        _set_default_variant_wrap
    )

    def get_pre_create_attr_defs(self):
        """Plugin attribute definitions needed for creation.
        Attribute definitions of plugin that define how creation will work.
        Values of these definitions are passed to `create` method.

        Note:
            Convert method should be implemented which should care about
            updating keys/values when plugin attributes change.

        Returns:
            list[AbstractAttrDef]: Attribute definitions that can be tweaked
                for created instance.
        """
        return self.pre_create_attr_defs

    def get_staging_dir(self, instance) -> Optional[StagingDir]:
        """Return the staging dir and persistence from instance.

        Args:
            instance (CreatedInstance): Instance for which should be staging
                dir gathered.

        Returns:
            Optional[namedtuple]: Staging dir path and persistence or None
        """
        create_ctx = self.create_context
        product_name = instance.get("productName")
        product_type = instance.get("productType")
        folder_path = instance.get("folderPath")

        # this can only work if product name and folder path are available
        if not product_name or not folder_path:
            return None

        publish_settings = self.project_settings["core"]["publish"]
        follow_workfile_version = (
            publish_settings
            ["CollectAnatomyInstanceData"]
            ["follow_workfile_version"]
        )
        follow_version_hosts = (
            publish_settings
            ["CollectSceneVersion"]
            ["hosts"]
        )

        current_host = create_ctx.host.name
        follow_workfile_version = (
            follow_workfile_version and
            current_host in follow_version_hosts
        )

        # Gather version number provided from the instance.
        current_workfile = create_ctx.get_current_workfile_path()
        version = instance.get("version")

        # If follow workfile, gather version from workfile path.
        if version is None and follow_workfile_version and current_workfile:
            workfile_version = get_version_from_path(current_workfile)
            if workfile_version is not None:
                version = int(workfile_version)

        # Fill-up version with next version available.
        if version is None:
            versions = self.get_next_versions_for_instances(
                [instance]
            )
            version, = tuple(versions.values())

        template_data = {"version": version}

        staging_dir_info = get_staging_dir_info(
            create_ctx.get_current_project_entity(),
            create_ctx.get_folder_entity(folder_path),
            create_ctx.get_task_entity(folder_path, instance.get("task")),
            product_type,
            product_name,
            create_ctx.host_name,
            anatomy=create_ctx.get_current_project_anatomy(),
            project_settings=create_ctx.get_current_project_settings(),
            always_return_path=False,
            logger=self.log,
            template_data=template_data,
        )

        return staging_dir_info or None

    def apply_staging_dir(self, instance):
        """Apply staging dir with persistence to instance's transient data.

        Method is called on instance creation and on instance update.

        Args:
            instance (CreatedInstance): Instance for which should be staging
                dir applied.

        Returns:
            Optional[str]: Staging dir path or None if not applied.
        """
        staging_dir_info = self.get_staging_dir(instance)
        if staging_dir_info is None:
            return None

        # path might be already created by get_staging_dir_info
        staging_dir_path = staging_dir_info.directory
        os.makedirs(staging_dir_path, exist_ok=True)

        instance.transient_data.update({
            "stagingDir": staging_dir_path,
            "stagingDir_persistent": staging_dir_info.is_persistent,
            "stagingDir_is_custom": staging_dir_info.is_custom,
        })

        self.log.info(f"Applied staging dir to instance: {staging_dir_path}")

        return staging_dir_path

    def _pre_create_attr_defs_changed(self):
        """Called when pre-create attribute definitions change.

        Create plugin can call this method when knows that
            'get_pre_create_attr_defs' should be called again.
        """
        self.create_context.create_plugin_pre_create_attr_defs_changed(
            self.identifier
        )

show_order property

Order in which is creator shown in UI.

Returns:

Name Type Description
int

Order in which is creator shown (less == earlier). By default is using Creator's 'order' or processing.

apply_staging_dir(instance)

Apply staging dir with persistence to instance's transient data.

Method is called on instance creation and on instance update.

Parameters:

Name Type Description Default
instance CreatedInstance

Instance for which should be staging dir applied.

required

Returns:

Type Description

Optional[str]: Staging dir path or None if not applied.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
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
def apply_staging_dir(self, instance):
    """Apply staging dir with persistence to instance's transient data.

    Method is called on instance creation and on instance update.

    Args:
        instance (CreatedInstance): Instance for which should be staging
            dir applied.

    Returns:
        Optional[str]: Staging dir path or None if not applied.
    """
    staging_dir_info = self.get_staging_dir(instance)
    if staging_dir_info is None:
        return None

    # path might be already created by get_staging_dir_info
    staging_dir_path = staging_dir_info.directory
    os.makedirs(staging_dir_path, exist_ok=True)

    instance.transient_data.update({
        "stagingDir": staging_dir_path,
        "stagingDir_persistent": staging_dir_info.is_persistent,
        "stagingDir_is_custom": staging_dir_info.is_custom,
    })

    self.log.info(f"Applied staging dir to instance: {staging_dir_path}")

    return staging_dir_path

create(product_name, instance_data, pre_create_data) abstractmethod

Create new instance and store it.

Ideally should be stored to workfile using host implementation.

Parameters:

Name Type Description Default
product_name(str)

Product name of created instance.

required
instance_data(dict)

Base data for instance.

required
pre_create_data(dict)

Data based on pre creation attributes. Those may affect how creator works.

required
Source code in client/ayon_core/pipeline/create/creator_plugins.py
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
@abstractmethod
def create(self, product_name, instance_data, pre_create_data):
    """Create new instance and store it.

    Ideally should be stored to workfile using host implementation.

    Args:
        product_name(str): Product name of created instance.
        instance_data(dict): Base data for instance.
        pre_create_data(dict): Data based on pre creation attributes.
            Those may affect how creator works.
    """

    # instance = CreatedInstance(
    #     self.product_type, product_name, instance_data
    # )
    pass

get_default_variant(only_explicit=False)

Default variant value that will be used to prefill variant input.

This is for user input and value may not be content of result from get_default_variants.

Note

This method does not allow to have empty string as default variant.

Parameters:

Name Type Description Default
only_explicit Optional[bool]

If True, only explicit default variant from '_default_variant' will be returned.

False

Returns:

Name Type Description
str

Variant value.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
def get_default_variant(self, only_explicit=False):
    """Default variant value that will be used to prefill variant input.

    This is for user input and value may not be content of result from
    `get_default_variants`.

    Note:
        This method does not allow to have empty string as
            default variant.

    Args:
        only_explicit (Optional[bool]): If True, only explicit default
            variant from '_default_variant' will be returned.

    Returns:
        str: Variant value.
    """

    if only_explicit or self._default_variant:
        return self._default_variant

    for variant in self.get_default_variants():
        return variant
    return DEFAULT_VARIANT_VALUE

get_default_variants()

Default variant values for UI tooltips.

Replacement of default_variants attribute. Using method gives ability to have some "logic" other than attribute values.

By default, returns default_variants value.

Returns:

Type Description

list[str]: Whisper variants for user input.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
753
754
755
756
757
758
759
760
761
762
763
764
765
def get_default_variants(self):
    """Default variant values for UI tooltips.

    Replacement of `default_variants` attribute. Using method gives
    ability to have some "logic" other than attribute values.

    By default, returns `default_variants` value.

    Returns:
        list[str]: Whisper variants for user input.
    """

    return copy.deepcopy(self.default_variants)

get_description()

Short description of product type and plugin.

Returns:

Name Type Description
str

Short description of product type.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
733
734
735
736
737
738
739
740
def get_description(self):
    """Short description of product type and plugin.

    Returns:
        str: Short description of product type.
    """

    return self.description

get_detail_description()

Description of product type and plugin.

Can be detailed with markdown or html tags.

Returns:

Name Type Description
str

Detailed description of product type for artist.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
742
743
744
745
746
747
748
749
750
751
def get_detail_description(self):
    """Description of product type and plugin.

    Can be detailed with markdown or html tags.

    Returns:
        str: Detailed description of product type for artist.
    """

    return self.detailed_description

get_pre_create_attr_defs()

Plugin attribute definitions needed for creation. Attribute definitions of plugin that define how creation will work. Values of these definitions are passed to create method.

Note

Convert method should be implemented which should care about updating keys/values when plugin attributes change.

Returns:

Type Description

list[AbstractAttrDef]: Attribute definitions that can be tweaked for created instance.

Source code in client/ayon_core/pipeline/create/creator_plugins.py
825
826
827
828
829
830
831
832
833
834
835
836
837
838
def get_pre_create_attr_defs(self):
    """Plugin attribute definitions needed for creation.
    Attribute definitions of plugin that define how creation will work.
    Values of these definitions are passed to `create` method.

    Note:
        Convert method should be implemented which should care about
        updating keys/values when plugin attributes change.

    Returns:
        list[AbstractAttrDef]: Attribute definitions that can be tweaked
            for created instance.
    """
    return self.pre_create_attr_defs

get_staging_dir(instance)

Return the staging dir and persistence from instance.

Parameters:

Name Type Description Default
instance CreatedInstance

Instance for which should be staging dir gathered.

required

Returns:

Type Description
Optional[StagingDir]

Optional[namedtuple]: Staging dir path and persistence or None

Source code in client/ayon_core/pipeline/create/creator_plugins.py
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
def get_staging_dir(self, instance) -> Optional[StagingDir]:
    """Return the staging dir and persistence from instance.

    Args:
        instance (CreatedInstance): Instance for which should be staging
            dir gathered.

    Returns:
        Optional[namedtuple]: Staging dir path and persistence or None
    """
    create_ctx = self.create_context
    product_name = instance.get("productName")
    product_type = instance.get("productType")
    folder_path = instance.get("folderPath")

    # this can only work if product name and folder path are available
    if not product_name or not folder_path:
        return None

    publish_settings = self.project_settings["core"]["publish"]
    follow_workfile_version = (
        publish_settings
        ["CollectAnatomyInstanceData"]
        ["follow_workfile_version"]
    )
    follow_version_hosts = (
        publish_settings
        ["CollectSceneVersion"]
        ["hosts"]
    )

    current_host = create_ctx.host.name
    follow_workfile_version = (
        follow_workfile_version and
        current_host in follow_version_hosts
    )

    # Gather version number provided from the instance.
    current_workfile = create_ctx.get_current_workfile_path()
    version = instance.get("version")

    # If follow workfile, gather version from workfile path.
    if version is None and follow_workfile_version and current_workfile:
        workfile_version = get_version_from_path(current_workfile)
        if workfile_version is not None:
            version = int(workfile_version)

    # Fill-up version with next version available.
    if version is None:
        versions = self.get_next_versions_for_instances(
            [instance]
        )
        version, = tuple(versions.values())

    template_data = {"version": version}

    staging_dir_info = get_staging_dir_info(
        create_ctx.get_current_project_entity(),
        create_ctx.get_folder_entity(folder_path),
        create_ctx.get_task_entity(folder_path, instance.get("task")),
        product_type,
        product_name,
        create_ctx.host_name,
        anatomy=create_ctx.get_current_project_anatomy(),
        project_settings=create_ctx.get_current_project_settings(),
        always_return_path=False,
        logger=self.log,
        template_data=template_data,
    )

    return staging_dir_info or None

CreatorError

Bases: Exception

Should be raised when creator failed because of known issue.

Message of error should be artist friendly.

Source code in client/ayon_core/pipeline/create/exceptions.py
63
64
65
66
67
68
class CreatorError(Exception):
    """Should be raised when creator failed because of known issue.

    Message of error should be artist friendly.
    """
    pass

IncompatibleLoaderError

Bases: ValueError

Error when Loader is incompatible with a representation.

Source code in client/ayon_core/pipeline/load/utils.py
58
59
60
class IncompatibleLoaderError(ValueError):
    """Error when Loader is incompatible with a representation."""
    pass

InventoryAction

Bases: object

A custom action for the scene inventory tool

If registered the action will be visible in the Right Mouse Button menu under the submenu "Actions".

Source code in client/ayon_core/pipeline/actions.py
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
class InventoryAction(object):
    """A custom action for the scene inventory tool

    If registered the action will be visible in the Right Mouse Button menu
    under the submenu "Actions".

    """

    label = None
    icon = None
    color = None
    order = 0

    log = logging.getLogger("InventoryAction")
    log.propagate = True

    @staticmethod
    def is_compatible(container):
        """Override function in a custom class

        This method is specifically used to ensure the action can operate on
        the container.

        Args:
            container(dict): the data of a loaded asset, see host.ls()

        Returns:
            bool
        """
        return bool(container.get("objectName"))

    def process(self, containers):
        """Override function in a custom class

        This method will receive all containers even those which are
        incompatible. It is advised to create a small filter along the lines
        of this example:

        valid_containers = filter(self.is_compatible(c) for c in containers)

        The return value will need to be a True-ish value to trigger
        the data_changed signal in order to refresh the view.

        You can return a list of container names to trigger GUI to select
        treeview items.

        You can return a dict to carry extra GUI options. For example:
            {
                "objectNames": [container names...],
                "options": {"mode": "toggle",
                            "clear": False}
            }
        Currently workable GUI options are:
            - clear (bool): Clear current selection before selecting by action.
                            Default `True`.
            - mode (str): selection mode, use one of these:
                          "select", "deselect", "toggle". Default is "select".

        Args:
            containers (list): list of dictionaries

        Return:
            bool, list or dict

        """
        return True

    @classmethod
    def filepath_from_context(cls, context):
        return get_representation_path_from_context(context)

is_compatible(container) staticmethod

Override function in a custom class

This method is specifically used to ensure the action can operate on the container.

Parameters:

Name Type Description Default
container(dict)

the data of a loaded asset, see host.ls()

required

Returns:

Type Description

bool

Source code in client/ayon_core/pipeline/actions.py
366
367
368
369
370
371
372
373
374
375
376
377
378
379
@staticmethod
def is_compatible(container):
    """Override function in a custom class

    This method is specifically used to ensure the action can operate on
    the container.

    Args:
        container(dict): the data of a loaded asset, see host.ls()

    Returns:
        bool
    """
    return bool(container.get("objectName"))

process(containers)

Override function in a custom class

This method will receive all containers even those which are incompatible. It is advised to create a small filter along the lines of this example:

valid_containers = filter(self.is_compatible(c) for c in containers)

The return value will need to be a True-ish value to trigger the data_changed signal in order to refresh the view.

You can return a list of container names to trigger GUI to select treeview items.

You can return a dict to carry extra GUI options. For example: { "objectNames": [container names...], "options": {"mode": "toggle", "clear": False} } Currently workable GUI options are: - clear (bool): Clear current selection before selecting by action. Default True. - mode (str): selection mode, use one of these: "select", "deselect", "toggle". Default is "select".

Parameters:

Name Type Description Default
containers list

list of dictionaries

required
Return

bool, list or dict

Source code in client/ayon_core/pipeline/actions.py
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
def process(self, containers):
    """Override function in a custom class

    This method will receive all containers even those which are
    incompatible. It is advised to create a small filter along the lines
    of this example:

    valid_containers = filter(self.is_compatible(c) for c in containers)

    The return value will need to be a True-ish value to trigger
    the data_changed signal in order to refresh the view.

    You can return a list of container names to trigger GUI to select
    treeview items.

    You can return a dict to carry extra GUI options. For example:
        {
            "objectNames": [container names...],
            "options": {"mode": "toggle",
                        "clear": False}
        }
    Currently workable GUI options are:
        - clear (bool): Clear current selection before selecting by action.
                        Default `True`.
        - mode (str): selection mode, use one of these:
                      "select", "deselect", "toggle". Default is "select".

    Args:
        containers (list): list of dictionaries

    Return:
        bool, list or dict

    """
    return True

KnownPublishError

Bases: Exception

Publishing crashed because of known error.

Artist can't affect source of the error.

Deprecated

Please use PublishError instead. Marked as deprecated 24/09/02.

Source code in client/ayon_core/pipeline/publish/publish_plugins.py
36
37
38
39
40
41
42
43
44
45
class KnownPublishError(Exception):
    """Publishing crashed because of known error.

    Artist can't affect source of the error.

    Deprecated:
        Please use `PublishError` instead. Marked as deprecated 24/09/02.

    """
    pass

LauncherAction

Bases: object

A custom action available

Source code in client/ayon_core/pipeline/actions.py
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
class LauncherAction(object):
    """A custom action available"""
    name = None
    label = None
    icon = None
    color = None
    order = 0

    log = logging.getLogger("LauncherAction")
    log.propagate = True

    def is_compatible(self, selection):
        """Return whether the class is compatible with the Session.

        Args:
            selection (LauncherActionSelection): Data with selection.

        """
        return True

    def process(self, selection, **kwargs):
        """Process the action.

        Args:
            selection (LauncherActionSelection): Data with selection.
            **kwargs: Additional arguments.

        """
        pass

is_compatible(selection)

Return whether the class is compatible with the Session.

Parameters:

Name Type Description Default
selection LauncherActionSelection

Data with selection.

required
Source code in client/ayon_core/pipeline/actions.py
330
331
332
333
334
335
336
337
def is_compatible(self, selection):
    """Return whether the class is compatible with the Session.

    Args:
        selection (LauncherActionSelection): Data with selection.

    """
    return True

process(selection, **kwargs)

Process the action.

Parameters:

Name Type Description Default
selection LauncherActionSelection

Data with selection.

required
**kwargs

Additional arguments.

{}
Source code in client/ayon_core/pipeline/actions.py
339
340
341
342
343
344
345
346
347
def process(self, selection, **kwargs):
    """Process the action.

    Args:
        selection (LauncherActionSelection): Data with selection.
        **kwargs: Additional arguments.

    """
    pass

LegacyCreator

Determine how assets are created

Source code in client/ayon_core/pipeline/create/legacy_create.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 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
class LegacyCreator:
    """Determine how assets are created"""
    label = None
    product_type = None
    defaults = None
    maintain_selection = True
    enabled = True

    dynamic_product_name_keys = []

    log = logging.getLogger("LegacyCreator")
    log.propagate = True

    def __init__(self, name, folder_path, options=None, data=None):
        self.name = name  # For backwards compatibility
        self.options = options

        # Default data
        self.data = collections.OrderedDict()
        # TODO use 'AYON_INSTANCE_ID' when all hosts support it
        self.data["id"] = AYON_INSTANCE_ID
        self.data["productType"] = self.product_type
        self.data["folderPath"] = folder_path
        self.data["productName"] = name
        self.data["active"] = True

        self.data.update(data or {})

    @classmethod
    def apply_settings(cls, project_settings):
        """Apply AYON settings to a plugin class."""

        host_name = os.environ.get("AYON_HOST_NAME")
        plugin_type = "create"
        plugin_type_settings = (
            project_settings
            .get(host_name, {})
            .get(plugin_type, {})
        )
        global_type_settings = (
            project_settings
            .get("core", {})
            .get(plugin_type, {})
        )
        if not global_type_settings and not plugin_type_settings:
            return

        plugin_name = cls.__name__

        plugin_settings = None
        # Look for plugin settings in host specific settings
        if plugin_name in plugin_type_settings:
            plugin_settings = plugin_type_settings[plugin_name]

        # Look for plugin settings in global settings
        elif plugin_name in global_type_settings:
            plugin_settings = global_type_settings[plugin_name]

        if not plugin_settings:
            return

        cls.log.debug(">>> We have preset for {}".format(plugin_name))
        for option, value in plugin_settings.items():
            if option == "enabled" and value is False:
                cls.log.debug("  - is disabled by preset")
            else:
                cls.log.debug("  - setting `{}`: `{}`".format(option, value))
            setattr(cls, option, value)

    def process(self):
        pass

    @classmethod
    def get_dynamic_data(
        cls, project_name, folder_entity, task_entity, variant, host_name
    ):
        """Return dynamic data for current Creator plugin.

        By default return keys from `dynamic_product_name_keys` attribute
        as mapping to keep formatted template unchanged.

        ```
        dynamic_product_name_keys = ["my_key"]
        ---
        output = {
            "my_key": "{my_key}"
        }
        ```

        Dynamic keys may override default Creator keys (productType, task,
        folderPath, ...) but do it wisely if you need.

        All of keys will be converted into 3 variants unchanged, capitalized
        and all upper letters. Because of that are all keys lowered.

        This method can be modified to prefill some values just keep in mind it
        is class method.

        Args:
            project_name (str): Context's project name.
            folder_entity (dict[str, Any]): Folder entity.
            task_entity (dict[str, Any]): Task entity.
            variant (str): What is entered by user in creator tool.
            host_name (str): Name of host.

        Returns:
            dict: Fill data for product name template.
        """
        dynamic_data = {}
        for key in cls.dynamic_product_name_keys:
            key = key.lower()
            dynamic_data[key] = "{" + key + "}"
        return dynamic_data

    @classmethod
    def get_product_name(
        cls, project_name, folder_entity, task_entity, variant, host_name=None
    ):
        """Return product name created with entered arguments.

        Logic extracted from Creator tool. This method should give ability
        to get product name without the tool.

        TODO: Maybe change `variant` variable.

        By default is output concatenated product type with variant.

        Args:
            project_name (str): Context's project name.
            folder_entity (dict[str, Any]): Folder entity.
            task_entity (dict[str, Any]): Task entity.
            variant (str): What is entered by user in creator tool.
            host_name (str): Name of host.

        Returns:
            str: Formatted product name with entered arguments. Should match
                config's logic.
        """

        dynamic_data = cls.get_dynamic_data(
            project_name, folder_entity, task_entity, variant, host_name
        )
        task_name = task_type = None
        if task_entity:
            task_name = task_entity["name"]
            task_type = task_entity["taskType"]
        return get_product_name(
            project_name,
            task_name,
            task_type,
            host_name,
            cls.product_type,
            variant,
            dynamic_data=dynamic_data
        )

apply_settings(project_settings) classmethod

Apply AYON settings to a plugin class.

Source code in client/ayon_core/pipeline/create/legacy_create.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
@classmethod
def apply_settings(cls, project_settings):
    """Apply AYON settings to a plugin class."""

    host_name = os.environ.get("AYON_HOST_NAME")
    plugin_type = "create"
    plugin_type_settings = (
        project_settings
        .get(host_name, {})
        .get(plugin_type, {})
    )
    global_type_settings = (
        project_settings
        .get("core", {})
        .get(plugin_type, {})
    )
    if not global_type_settings and not plugin_type_settings:
        return

    plugin_name = cls.__name__

    plugin_settings = None
    # Look for plugin settings in host specific settings
    if plugin_name in plugin_type_settings:
        plugin_settings = plugin_type_settings[plugin_name]

    # Look for plugin settings in global settings
    elif plugin_name in global_type_settings:
        plugin_settings = global_type_settings[plugin_name]

    if not plugin_settings:
        return

    cls.log.debug(">>> We have preset for {}".format(plugin_name))
    for option, value in plugin_settings.items():
        if option == "enabled" and value is False:
            cls.log.debug("  - is disabled by preset")
        else:
            cls.log.debug("  - setting `{}`: `{}`".format(option, value))
        setattr(cls, option, value)

get_dynamic_data(project_name, folder_entity, task_entity, variant, host_name) classmethod

Return dynamic data for current Creator plugin.

By default return keys from dynamic_product_name_keys attribute as mapping to keep formatted template unchanged.

dynamic_product_name_keys = ["my_key"]
---
output = {
    "my_key": "{my_key}"
}

Dynamic keys may override default Creator keys (productType, task, folderPath, ...) but do it wisely if you need.

All of keys will be converted into 3 variants unchanged, capitalized and all upper letters. Because of that are all keys lowered.

This method can be modified to prefill some values just keep in mind it is class method.

Parameters:

Name Type Description Default
project_name str

Context's project name.

required
folder_entity dict[str, Any]

Folder entity.

required
task_entity dict[str, Any]

Task entity.

required
variant str

What is entered by user in creator tool.

required
host_name str

Name of host.

required

Returns:

Name Type Description
dict

Fill data for product name template.

Source code in client/ayon_core/pipeline/create/legacy_create.py
 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
@classmethod
def get_dynamic_data(
    cls, project_name, folder_entity, task_entity, variant, host_name
):
    """Return dynamic data for current Creator plugin.

    By default return keys from `dynamic_product_name_keys` attribute
    as mapping to keep formatted template unchanged.

    ```
    dynamic_product_name_keys = ["my_key"]
    ---
    output = {
        "my_key": "{my_key}"
    }
    ```

    Dynamic keys may override default Creator keys (productType, task,
    folderPath, ...) but do it wisely if you need.

    All of keys will be converted into 3 variants unchanged, capitalized
    and all upper letters. Because of that are all keys lowered.

    This method can be modified to prefill some values just keep in mind it
    is class method.

    Args:
        project_name (str): Context's project name.
        folder_entity (dict[str, Any]): Folder entity.
        task_entity (dict[str, Any]): Task entity.
        variant (str): What is entered by user in creator tool.
        host_name (str): Name of host.

    Returns:
        dict: Fill data for product name template.
    """
    dynamic_data = {}
    for key in cls.dynamic_product_name_keys:
        key = key.lower()
        dynamic_data[key] = "{" + key + "}"
    return dynamic_data

get_product_name(project_name, folder_entity, task_entity, variant, host_name=None) classmethod

Return product name created with entered arguments.

Logic extracted from Creator tool. This method should give ability to get product name without the tool.

TODO: Maybe change variant variable.

By default is output concatenated product type with variant.

Parameters:

Name Type Description Default
project_name str

Context's project name.

required
folder_entity dict[str, Any]

Folder entity.

required
task_entity dict[str, Any]

Task entity.

required
variant str

What is entered by user in creator tool.

required
host_name str

Name of host.

None

Returns:

Name Type Description
str

Formatted product name with entered arguments. Should match config's logic.

Source code in client/ayon_core/pipeline/create/legacy_create.py
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
@classmethod
def get_product_name(
    cls, project_name, folder_entity, task_entity, variant, host_name=None
):
    """Return product name created with entered arguments.

    Logic extracted from Creator tool. This method should give ability
    to get product name without the tool.

    TODO: Maybe change `variant` variable.

    By default is output concatenated product type with variant.

    Args:
        project_name (str): Context's project name.
        folder_entity (dict[str, Any]): Folder entity.
        task_entity (dict[str, Any]): Task entity.
        variant (str): What is entered by user in creator tool.
        host_name (str): Name of host.

    Returns:
        str: Formatted product name with entered arguments. Should match
            config's logic.
    """

    dynamic_data = cls.get_dynamic_data(
        project_name, folder_entity, task_entity, variant, host_name
    )
    task_name = task_type = None
    if task_entity:
        task_name = task_entity["name"]
        task_type = task_entity["taskType"]
    return get_product_name(
        project_name,
        task_name,
        task_type,
        host_name,
        cls.product_type,
        variant,
        dynamic_data=dynamic_data
    )

LoaderPlugin

Bases: list

Load representation into host application

Source code in client/ayon_core/pipeline/load/plugins.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
class LoaderPlugin(list):
    """Load representation into host application"""

    product_types = set()
    representations = set()
    extensions = {"*"}
    order = 0
    is_multiple_contexts_compatible = False
    enabled = True

    options = []

    log = logging.getLogger("ProductLoader")
    log.propagate = True

    @classmethod
    def apply_settings(cls, project_settings):
        host_name = os.environ.get("AYON_HOST_NAME")
        plugin_type = "load"
        plugin_type_settings = (
            project_settings
            .get(host_name, {})
            .get(plugin_type, {})
        )
        global_type_settings = (
            project_settings
            .get("core", {})
            .get(plugin_type, {})
        )
        if not global_type_settings and not plugin_type_settings:
            return

        plugin_name = cls.__name__

        plugin_settings = None
        # Look for plugin settings in host specific settings
        if plugin_name in plugin_type_settings:
            plugin_settings = plugin_type_settings[plugin_name]

        # Look for plugin settings in global settings
        elif plugin_name in global_type_settings:
            plugin_settings = global_type_settings[plugin_name]

        if not plugin_settings:
            return

        print(">>> We have preset for {}".format(plugin_name))
        for option, value in plugin_settings.items():
            if option == "enabled" and value is False:
                print("  - is disabled by preset")
            else:
                print("  - setting `{}`: `{}`".format(option, value))
            setattr(cls, option, value)

    @classmethod
    def has_valid_extension(cls, repre_entity):
        """Has representation document valid extension for loader.

        Args:
            repre_entity (dict[str, Any]): Representation entity.

        Returns:
             bool: Representation has valid extension
        """

        if "*" in cls.extensions:
            return True

        # Get representation main file extension from 'context'
        repre_context = repre_entity.get("context") or {}
        ext = repre_context.get("ext")
        if not ext:
            # Legacy way how to get extensions
            path = repre_entity.get("attrib", {}).get("path")
            if not path:
                cls.log.info(
                    "Representation doesn't have known source of extension"
                    " information."
                )
                return False

            cls.log.debug("Using legacy source of extension from path.")
            ext = os.path.splitext(path)[-1].lstrip(".")

        # If representation does not have extension then can't be valid
        if not ext:
            return False

        valid_extensions_low = {ext.lower() for ext in cls.extensions}
        return ext.lower() in valid_extensions_low

    @classmethod
    def is_compatible_loader(cls, context):
        """Return whether a loader is compatible with a context.

        On override make sure it is overridden as class or static method.

        This checks the product type and the representation for the given
        loader plugin.

        Args:
            context (dict[str, Any]): Documents of context for which should
                be loader used.

        Returns:
            bool: Is loader compatible for context.
        """

        plugin_repre_names = cls.get_representations()
        plugin_product_types = cls.product_types
        if (
            not plugin_repre_names
            or not plugin_product_types
            or not cls.extensions
        ):
            return False

        repre_entity = context.get("representation")
        if not repre_entity:
            return False

        plugin_repre_names = set(plugin_repre_names)
        if (
            "*" not in plugin_repre_names
            and repre_entity["name"] not in plugin_repre_names
        ):
            return False

        if not cls.has_valid_extension(repre_entity):
            return False

        plugin_product_types = set(plugin_product_types)
        if "*" in plugin_product_types:
            return True

        product_entity = context["product"]
        product_type = product_entity["productType"]

        return product_type in plugin_product_types

    @classmethod
    def get_representations(cls):
        """Representation names with which is plugin compatible.

        Empty set makes the plugin incompatible with any representation. To
            allow compatibility with all representations use '{"*"}'.

        Returns:
            set[str]: Names with which is plugin compatible.

        """
        return cls.representations

    @classmethod
    def filepath_from_context(cls, context):
        return get_representation_path_from_context(context)

    def load(self, context, name=None, namespace=None, options=None):
        """Load asset via database

        Arguments:
            context (dict): Full parenthood of representation to load
            name (str, optional): Use pre-defined name
            namespace (str, optional): Use pre-defined namespace
            options (dict, optional): Additional settings dictionary

        """
        raise NotImplementedError("Loader.load() must be "
                                  "implemented by subclass")

    def update(self, container, context):
        """Update `container` to `representation`

        Args:
            container (avalon-core:container-1.0): Container to update,
                from `host.ls()`.
            context (dict): Update the container to this representation.

        """
        raise NotImplementedError("Loader.update() must be "
                                  "implemented by subclass")

    def remove(self, container):
        """Remove a container

        Arguments:
            container (avalon-core:container-1.0): Container to remove,
                from `host.ls()`.

        Returns:
            bool: Whether the container was deleted

        """

        raise NotImplementedError("Loader.remove() must be "
                                  "implemented by subclass")

    @classmethod
    def get_options(cls, contexts):
        """
            Returns static (cls) options or could collect from 'contexts'.

            Args:
                contexts (list): of repre or product contexts
            Returns:
                (list)
        """
        return cls.options or []

    @property
    def fname(self):
        """Backwards compatibility with deprecation warning"""

        self.log.warning((
            "DEPRECATION WARNING: Source - Loader plugin {}."
            " The 'fname' property on the Loader plugin will be removed in"
            " future versions of OpenPype. Planned version to drop the support"
            " is 3.16.6 or 3.17.0."
        ).format(self.__class__.__name__))
        if hasattr(self, "_fname"):
            return self._fname

    @classmethod
    def get_representation_name_aliases(cls, representation_name: str):
        """Return representation names to which switching is allowed from
        the input representation name, like an alias replacement of the input
        `representation_name`.

        For example, to allow an automated switch on update from representation
        `ma` to `mb` or `abc`, then when `representation_name` is `ma` return:
            ["mb", "abc"]

        The order of the names in the returned representation names is
        important, because the first one existing under the new version will
        be chosen.

        Returns:
            List[str]: Representation names switching to is allowed on update
              if the input representation name is not found on the new version.
        """
        return []

fname property

Backwards compatibility with deprecation warning

get_options(contexts) classmethod

Returns static (cls) options or could collect from 'contexts'.

Parameters:

Name Type Description Default
contexts list

of repre or product contexts

required

Returns: (list)

Source code in client/ayon_core/pipeline/load/plugins.py
212
213
214
215
216
217
218
219
220
221
222
@classmethod
def get_options(cls, contexts):
    """
        Returns static (cls) options or could collect from 'contexts'.

        Args:
            contexts (list): of repre or product contexts
        Returns:
            (list)
    """
    return cls.options or []

get_representation_name_aliases(representation_name) classmethod

Return representation names to which switching is allowed from the input representation name, like an alias replacement of the input representation_name.

For example, to allow an automated switch on update from representation ma to mb or abc, then when representation_name is ma return: ["mb", "abc"]

The order of the names in the returned representation names is important, because the first one existing under the new version will be chosen.

Returns:

Type Description

List[str]: Representation names switching to is allowed on update if the input representation name is not found on the new version.

Source code in client/ayon_core/pipeline/load/plugins.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
@classmethod
def get_representation_name_aliases(cls, representation_name: str):
    """Return representation names to which switching is allowed from
    the input representation name, like an alias replacement of the input
    `representation_name`.

    For example, to allow an automated switch on update from representation
    `ma` to `mb` or `abc`, then when `representation_name` is `ma` return:
        ["mb", "abc"]

    The order of the names in the returned representation names is
    important, because the first one existing under the new version will
    be chosen.

    Returns:
        List[str]: Representation names switching to is allowed on update
          if the input representation name is not found on the new version.
    """
    return []

get_representations() classmethod

Representation names with which is plugin compatible.

Empty set makes the plugin incompatible with any representation. To allow compatibility with all representations use '{"*"}'.

Returns:

Type Description

set[str]: Names with which is plugin compatible.

Source code in client/ayon_core/pipeline/load/plugins.py
155
156
157
158
159
160
161
162
163
164
165
166
@classmethod
def get_representations(cls):
    """Representation names with which is plugin compatible.

    Empty set makes the plugin incompatible with any representation. To
        allow compatibility with all representations use '{"*"}'.

    Returns:
        set[str]: Names with which is plugin compatible.

    """
    return cls.representations

has_valid_extension(repre_entity) classmethod

Has representation document valid extension for loader.

Parameters:

Name Type Description Default
repre_entity dict[str, Any]

Representation entity.

required

Returns:

Name Type Description
bool

Representation has valid extension

Source code in client/ayon_core/pipeline/load/plugins.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
@classmethod
def has_valid_extension(cls, repre_entity):
    """Has representation document valid extension for loader.

    Args:
        repre_entity (dict[str, Any]): Representation entity.

    Returns:
         bool: Representation has valid extension
    """

    if "*" in cls.extensions:
        return True

    # Get representation main file extension from 'context'
    repre_context = repre_entity.get("context") or {}
    ext = repre_context.get("ext")
    if not ext:
        # Legacy way how to get extensions
        path = repre_entity.get("attrib", {}).get("path")
        if not path:
            cls.log.info(
                "Representation doesn't have known source of extension"
                " information."
            )
            return False

        cls.log.debug("Using legacy source of extension from path.")
        ext = os.path.splitext(path)[-1].lstrip(".")

    # If representation does not have extension then can't be valid
    if not ext:
        return False

    valid_extensions_low = {ext.lower() for ext in cls.extensions}
    return ext.lower() in valid_extensions_low

is_compatible_loader(context) classmethod

Return whether a loader is compatible with a context.

On override make sure it is overridden as class or static method.

This checks the product type and the representation for the given loader plugin.

Parameters:

Name Type Description Default
context dict[str, Any]

Documents of context for which should be loader used.

required

Returns:

Name Type Description
bool

Is loader compatible for context.

Source code in client/ayon_core/pipeline/load/plugins.py
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
@classmethod
def is_compatible_loader(cls, context):
    """Return whether a loader is compatible with a context.

    On override make sure it is overridden as class or static method.

    This checks the product type and the representation for the given
    loader plugin.

    Args:
        context (dict[str, Any]): Documents of context for which should
            be loader used.

    Returns:
        bool: Is loader compatible for context.
    """

    plugin_repre_names = cls.get_representations()
    plugin_product_types = cls.product_types
    if (
        not plugin_repre_names
        or not plugin_product_types
        or not cls.extensions
    ):
        return False

    repre_entity = context.get("representation")
    if not repre_entity:
        return False

    plugin_repre_names = set(plugin_repre_names)
    if (
        "*" not in plugin_repre_names
        and repre_entity["name"] not in plugin_repre_names
    ):
        return False

    if not cls.has_valid_extension(repre_entity):
        return False

    plugin_product_types = set(plugin_product_types)
    if "*" in plugin_product_types:
        return True

    product_entity = context["product"]
    product_type = product_entity["productType"]

    return product_type in plugin_product_types

load(context, name=None, namespace=None, options=None)

Load asset via database

Parameters:

Name Type Description Default
context dict

Full parenthood of representation to load

required
name str

Use pre-defined name

None
namespace str

Use pre-defined namespace

None
options dict

Additional settings dictionary

None
Source code in client/ayon_core/pipeline/load/plugins.py
172
173
174
175
176
177
178
179
180
181
182
183
def load(self, context, name=None, namespace=None, options=None):
    """Load asset via database

    Arguments:
        context (dict): Full parenthood of representation to load
        name (str, optional): Use pre-defined name
        namespace (str, optional): Use pre-defined namespace
        options (dict, optional): Additional settings dictionary

    """
    raise NotImplementedError("Loader.load() must be "
                              "implemented by subclass")

remove(container)

Remove a container

Parameters:

Name Type Description Default
container avalon - core

container-1.0): Container to remove, from host.ls().

required

Returns:

Name Type Description
bool

Whether the container was deleted

Source code in client/ayon_core/pipeline/load/plugins.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def remove(self, container):
    """Remove a container

    Arguments:
        container (avalon-core:container-1.0): Container to remove,
            from `host.ls()`.

    Returns:
        bool: Whether the container was deleted

    """

    raise NotImplementedError("Loader.remove() must be "
                              "implemented by subclass")

update(container, context)

Update container to representation

Parameters:

Name Type Description Default
container avalon - core

container-1.0): Container to update, from host.ls().

required
context dict

Update the container to this representation.

required
Source code in client/ayon_core/pipeline/load/plugins.py
185
186
187
188
189
190
191
192
193
194
195
def update(self, container, context):
    """Update `container` to `representation`

    Args:
        container (avalon-core:container-1.0): Container to update,
            from `host.ls()`.
        context (dict): Update the container to this representation.

    """
    raise NotImplementedError("Loader.update() must be "
                              "implemented by subclass")

OptionalPyblishPluginMixin

Bases: AYONPyblishPluginMixin

Prepare mixin for optional plugins.

Defined active attribute definition prepared for published and prepares method which will check if is active or not.

class ValidateScene(
    pyblish.api.InstancePlugin, OptionalPyblishPluginMixin
):
    def process(self, instance):
        # Skip the instance if is not active by data on the instance
        if not self.is_active(instance.data):
            return
Source code in client/ayon_core/pipeline/publish/publish_plugins.py
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
class OptionalPyblishPluginMixin(AYONPyblishPluginMixin):
    """Prepare mixin for optional plugins.

    Defined active attribute definition prepared for published and
    prepares method which will check if is active or not.

    ```
    class ValidateScene(
        pyblish.api.InstancePlugin, OptionalPyblishPluginMixin
    ):
        def process(self, instance):
            # Skip the instance if is not active by data on the instance
            if not self.is_active(instance.data):
                return
    ```
    """

    # Allow exposing tooltip from class with `optional_tooltip` attribute
    optional_tooltip: Optional[str] = None

    @classmethod
    def get_attribute_defs(cls):
        """Attribute definitions based on plugin's optional attribute."""

        # Empty list if plugin is not optional
        if not getattr(cls, "optional", None):
            return []

        # Get active value from class as default value
        active = getattr(cls, "active", True)
        # Return boolean stored under 'active' key with label of the class name
        label = cls.label or cls.__name__

        return [
            BoolDef(
                "active",
                default=active,
                label=label,
                tooltip=cls.optional_tooltip,
            )
        ]

    def is_active(self, data):
        """Check if plugins is active for instance/context based on their data.

        Args:
            data(dict): Data from instance or context.
        """
        # Skip if is not optional and return True
        if not getattr(self, "optional", None):
            return True
        attr_values = self.get_attr_values_from_data(data)
        active = attr_values.get("active")
        if active is None:
            active = getattr(self, "active", True)
        return active

get_attribute_defs() classmethod

Attribute definitions based on plugin's optional attribute.

Source code in client/ayon_core/pipeline/publish/publish_plugins.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
@classmethod
def get_attribute_defs(cls):
    """Attribute definitions based on plugin's optional attribute."""

    # Empty list if plugin is not optional
    if not getattr(cls, "optional", None):
        return []

    # Get active value from class as default value
    active = getattr(cls, "active", True)
    # Return boolean stored under 'active' key with label of the class name
    label = cls.label or cls.__name__

    return [
        BoolDef(
            "active",
            default=active,
            label=label,
            tooltip=cls.optional_tooltip,
        )
    ]

is_active(data)

Check if plugins is active for instance/context based on their data.

Parameters:

Name Type Description Default
data(dict)

Data from instance or context.

required
Source code in client/ayon_core/pipeline/publish/publish_plugins.py
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def is_active(self, data):
    """Check if plugins is active for instance/context based on their data.

    Args:
        data(dict): Data from instance or context.
    """
    # Skip if is not optional and return True
    if not getattr(self, "optional", None):
        return True
    attr_values = self.get_attr_values_from_data(data)
    active = attr_values.get("active")
    if active is None:
        active = getattr(self, "active", True)
    return active

ProductLoaderPlugin

Bases: LoaderPlugin

Load product into host application Arguments: context (dict): avalon-core:context-1.0 name (str, optional): Use pre-defined name namespace (str, optional): Use pre-defined namespace

Source code in client/ayon_core/pipeline/load/plugins.py
258
259
260
261
262
263
264
class ProductLoaderPlugin(LoaderPlugin):
    """Load product into host application
    Arguments:
        context (dict): avalon-core:context-1.0
        name (str, optional): Use pre-defined name
        namespace (str, optional): Use pre-defined namespace
    """

PublishError

Bases: Exception

Publishing crashed because of known error.

Message will be shown in UI for artist.

Parameters:

Name Type Description Default
message str

Message of error. Short explanation an issue.

required
title Optional[str]

Title showed in UI.

None
description Optional[str]

Detailed description of an error. It is possible to use Markdown syntax.

None
Source code in client/ayon_core/pipeline/publish/publish_plugins.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class PublishError(Exception):
    """Publishing crashed because of known error.

    Message will be shown in UI for artist.

    Args:
        message (str): Message of error. Short explanation an issue.
        title (Optional[str]): Title showed in UI.
        description (Optional[str]): Detailed description of an error.
            It is possible to use Markdown syntax.

    """
    def __init__(self, message, title=None, description=None, detail=None):
        self.message = message
        self.title = title
        self.description = description or message
        self.detail = detail
        super().__init__(message)

PublishValidationError

Bases: PublishError

Validation error happened during publishing.

This exception should be used when validation publishing failed.

Publishing does not stop during validation order if this exception is raised.

Has additional UI specific attributes that may be handy for artist.

Argument 'title' is used to group errors.

Source code in client/ayon_core/pipeline/publish/publish_plugins.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class PublishValidationError(PublishError):
    """Validation error happened during publishing.

    This exception should be used when validation publishing failed.

    Publishing does not stop during validation order if this
        exception is raised.

    Has additional UI specific attributes that may be handy for artist.

    Argument 'title' is used to group errors.

    """
    pass

get_current_host_name()

Current host name.

Function is based on currently registered host integration or environment variable 'AYON_HOST_NAME'.

Returns:

Type Description

Union[str, None]: Name of host integration in current process or None.

Source code in client/ayon_core/pipeline/context_tools.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def get_current_host_name():
    """Current host name.

    Function is based on currently registered host integration or environment
    variable 'AYON_HOST_NAME'.

    Returns:
        Union[str, None]: Name of host integration in current process or None.
    """

    host = registered_host()
    if isinstance(host, HostBase):
        return host.name
    return os.environ.get("AYON_HOST_NAME")

get_global_context()

Global context defined in environment variables.

Values here may not reflect current context of host integration. The function can be used on startup before a host is registered.

Use 'get_current_context' to make sure you'll get current host integration context info.

Example::

{
    "project_name": "Commercial",
    "folder_path": "Bunny",
    "task_name": "Animation",
}

Returns:

Type Description

dict[str, Union[str, None]]: Context defined with environment variables.

Source code in client/ayon_core/pipeline/context_tools.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
def get_global_context():
    """Global context defined in environment variables.

    Values here may not reflect current context of host integration. The
    function can be used on startup before a host is registered.

    Use 'get_current_context' to make sure you'll get current host integration
    context info.

    Example::

        {
            "project_name": "Commercial",
            "folder_path": "Bunny",
            "task_name": "Animation",
        }

    Returns:
        dict[str, Union[str, None]]: Context defined with environment
            variables.
    """

    return {
        "project_name": os.environ.get("AYON_PROJECT_NAME"),
        "folder_path": os.environ.get("AYON_FOLDER_PATH"),
        "task_name": os.environ.get("AYON_TASK_NAME"),
    }

get_process_id()

Fake process id created on demand using uuid.

Can be used to create process specific folders in temp directory.

Returns:

Name Type Description
str

Process id.

Source code in client/ayon_core/pipeline/context_tools.py
569
570
571
572
573
574
575
576
577
578
579
580
581
def get_process_id():
    """Fake process id created on demand using uuid.

    Can be used to create process specific folders in temp directory.

    Returns:
        str: Process id.
    """

    global _process_id
    if _process_id is None:
        _process_id = str(uuid.uuid4())
    return _process_id

get_repres_contexts(representation_ids, project_name=None)

Return parenthood context for representation.

Parameters:

Name Type Description Default
representation_ids list

The representation ids.

required
project_name Optional[str]

Project name.

None

Returns:

Name Type Description
dict

The full representation context by representation id. keys are repre_id, value is dictionary with entities of folder, product, version and representation.

Source code in client/ayon_core/pipeline/load/utils.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
def get_repres_contexts(representation_ids, project_name=None):
    """Return parenthood context for representation.

    Args:
        representation_ids (list): The representation ids.
        project_name (Optional[str]): Project name.

    Returns:
        dict: The full representation context by representation id.
            keys are repre_id, value is dictionary with entities of
            folder, product, version and representation.
    """
    from ayon_core.pipeline import get_current_project_name

    if not representation_ids:
        return {}

    if not project_name:
        project_name = get_current_project_name()

    repre_entities = ayon_api.get_representations(
        project_name, representation_ids
    )

    return get_representation_contexts(project_name, repre_entities)

get_representation_context(project_name, representation)

Return parenthood context for representation.

Parameters:

Name Type Description Default
project_name str

Project name.

required
representation Union[dict[str, Any], str]

Representation entity or representation id.

required

Returns:

Type Description

dict[str, dict[str, Any]]: The full representation context.

Raises:

Type Description
ValueError

When representation is invalid or parents were not found.

Source code in client/ayon_core/pipeline/load/utils.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
def get_representation_context(project_name, representation):
    """Return parenthood context for representation.

    Args:
        project_name (str): Project name.
        representation (Union[dict[str, Any], str]): Representation entity
            or representation id.

    Returns:
        dict[str, dict[str, Any]]: The full representation context.

    Raises:
        ValueError: When representation is invalid or parents were not found.

    """
    if not representation:
        raise ValueError(
            "Invalid argument value {}".format(str(representation))
        )

    if isinstance(representation, dict):
        repre_entity = representation
        repre_id = repre_entity["id"]
        context = get_representation_contexts(
            project_name, [repre_entity]
        )[repre_id]
    else:
        repre_id = representation
        context = get_representation_contexts_by_ids(
            project_name, {repre_id}
        )[repre_id]

    missing_entities = []
    for key, value in context.items():
        if value is None:
            missing_entities.append(key)

    if missing_entities:
        raise ValueError(
            "Not able to receive representation parent types: {}".format(
                ", ".join(missing_entities)
            )
        )

    return context

get_representation_path(representation, root=None)

Get filename from representation document

There are three ways of getting the path from representation which are tried in following sequence until successful. 1. Get template from representation['data']['template'] and data from representation['context']. Then format template with the data. 2. Get template from project['config'] and format it with default data set 3. Get representation['data']['path'] and use it directly

Parameters:

Name Type Description Default
representation(dict)

representation document from the database

required

Returns:

Name Type Description
str

fullpath of the representation

Source code in client/ayon_core/pipeline/load/utils.py
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
def get_representation_path(representation, root=None):
    """Get filename from representation document

    There are three ways of getting the path from representation which are
    tried in following sequence until successful.
    1. Get template from representation['data']['template'] and data from
       representation['context']. Then format template with the data.
    2. Get template from project['config'] and format it with default data set
    3. Get representation['data']['path'] and use it directly

    Args:
        representation(dict): representation document from the database

    Returns:
        str: fullpath of the representation

    """

    if root is None:
        from ayon_core.pipeline import registered_root

        root = registered_root()

    def path_from_representation():
        try:
            template = representation["attrib"]["template"]
        except KeyError:
            return None

        try:
            context = representation["context"]

            _fix_representation_context_compatibility(context)

            context["root"] = root
            path = StringTemplate.format_strict_template(
                template, context
            )
            # Force replacing backslashes with forward slashed if not on
            #   windows
            if platform.system().lower() != "windows":
                path = path.replace("\\", "/")
        except (TemplateUnsolved, KeyError):
            # Template references unavailable data
            return None

        if not path:
            return path

        normalized_path = os.path.normpath(path)
        if os.path.exists(normalized_path):
            return normalized_path
        return path

    def path_from_data():
        if "path" not in representation["attrib"]:
            return None

        path = representation["attrib"]["path"]
        # Force replacing backslashes with forward slashed if not on
        #   windows
        if platform.system().lower() != "windows":
            path = path.replace("\\", "/")

        if os.path.exists(path):
            return os.path.normpath(path)

        dir_path, file_name = os.path.split(path)
        if not os.path.exists(dir_path):
            return

        base_name, ext = os.path.splitext(file_name)
        file_name_items = None
        if "#" in base_name:
            file_name_items = [part for part in base_name.split("#") if part]
        elif "%" in base_name:
            file_name_items = base_name.split("%")

        if not file_name_items:
            return

        filename_start = file_name_items[0]

        for _file in os.listdir(dir_path):
            if _file.startswith(filename_start) and _file.endswith(ext):
                return os.path.normpath(path)

    return (
        path_from_representation() or path_from_data()
    )

get_staging_dir_info(project_entity, folder_entity, task_entity, product_type, product_name, host_name, anatomy=None, project_settings=None, template_data=None, always_return_path=True, force_tmp_dir=False, logger=None, prefix=None, suffix=None, username=None)

Get staging dir info data.

If force_temp is set, staging dir will be created as tempdir. If always_get_some_dir is set, staging dir will be created as tempdir if no staging dir profile is found. If prefix or suffix is not set, default values will be used.

Parameters:

Name Type Description Default
project_entity Dict[str, Any]

Project entity.

required
folder_entity Optional[Dict[str, Any]]

Folder entity.

required
task_entity Optional[Dict[str, Any]]

Task entity.

required
product_type str

Type of product.

required
product_name str

Name of product.

required
host_name str

Name of host.

required
anatomy Optional[Anatomy]

Anatomy object.

None
project_settings Optional[Dict[str, Any]]

Prepared project settings.

None
template_data Optional[Dict[str, Any]]

Additional data for formatting staging dir template.

None
always_return_path Optional[bool]

If True, staging dir will be created as tempdir if no staging dir profile is found. Input value False will return None if no staging dir profile is found.

True
force_tmp_dir Optional[bool]

If True, staging dir will be created as tempdir.

False
logger Optional[Logger]

Logger instance.

None
suffix Optional[str]

Optional suffix for staging dir name.

None
username Optional[str]

AYON Username.

None

Returns:

Type Description
Optional[StagingDir]

Optional[StagingDir]: Staging dir info data

Source code in client/ayon_core/pipeline/staging_dir.py
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
def get_staging_dir_info(
    project_entity: Dict[str, Any],
    folder_entity: Optional[Dict[str, Any]],
    task_entity: Optional[Dict[str, Any]],
    product_type: str,
    product_name: str,
    host_name: str,
    anatomy: Optional[Anatomy] = None,
    project_settings: Optional[Dict[str, Any]] = None,
    template_data: Optional[Dict[str, Any]] = None,
    always_return_path: bool = True,
    force_tmp_dir: bool = False,
    logger: Optional[logging.Logger] = None,
    prefix: Optional[str] = None,
    suffix: Optional[str] = None,
    username: Optional[str] = None,
) -> Optional[StagingDir]:
    """Get staging dir info data.

    If `force_temp` is set, staging dir will be created as tempdir.
    If `always_get_some_dir` is set, staging dir will be created as tempdir if
    no staging dir profile is found.
    If `prefix` or `suffix` is not set, default values will be used.

    Arguments:
        project_entity (Dict[str, Any]): Project entity.
        folder_entity (Optional[Dict[str, Any]]): Folder entity.
        task_entity (Optional[Dict[str, Any]]): Task entity.
        product_type (str): Type of product.
        product_name (str): Name of product.
        host_name (str): Name of host.
        anatomy (Optional[Anatomy]): Anatomy object.
        project_settings (Optional[Dict[str, Any]]): Prepared project settings.
        template_data (Optional[Dict[str, Any]]): Additional data for
            formatting staging dir template.
        always_return_path (Optional[bool]): If True, staging dir will be
            created as tempdir if no staging dir profile is found. Input value
            False will return None if no staging dir profile is found.
        force_tmp_dir (Optional[bool]): If True, staging dir will be created as
            tempdir.
        logger (Optional[logging.Logger]): Logger instance.
        prefix (Optional[str]) Optional prefix for staging dir name.
        suffix (Optional[str]): Optional suffix for staging dir name.
        username (Optional[str]): AYON Username.

    Returns:
        Optional[StagingDir]: Staging dir info data

    """
    log = logger or Logger.get_logger("get_staging_dir_info")

    if anatomy is None:
        anatomy = Anatomy(
            project_entity["name"], project_entity=project_entity
        )

    if force_tmp_dir:
        return StagingDir(
            get_temp_dir(
                project_name=project_entity["name"],
                anatomy=anatomy,
                prefix=prefix,
                suffix=suffix,
            ),
            is_persistent=False,
            is_custom=False
        )

    # making few queries to database
    ctx_data = get_template_data(
        project_entity, folder_entity, task_entity, host_name,
        settings=project_settings,
        username=username
    )

    # add additional data
    ctx_data["product"] = {
        "type": product_type,
        "name": product_name
    }

    # add additional template formatting data
    if template_data:
        ctx_data.update(template_data)

    task_name = task_type = None
    if task_entity:
        task_name = task_entity["name"]
        task_type = task_entity["taskType"]

    # get staging dir config
    staging_dir_config = get_staging_dir_config(
        project_entity["name"],
        task_type,
        task_name ,
        product_type,
        product_name,
        host_name,
        project_settings=project_settings,
        anatomy=anatomy,
        log=log,
    )

    if staging_dir_config:
        dir_template = staging_dir_config["template"]["directory"]
        return StagingDir(
            dir_template.format_strict(ctx_data),
            is_persistent=staging_dir_config["persistence"],
            is_custom=True
        )

    # no config found but force an output
    if always_return_path:
        return StagingDir(
            get_temp_dir(
                project_name=project_entity["name"],
                anatomy=anatomy,
                prefix=prefix,
                suffix=suffix,
            ),
            is_persistent=False,
            is_custom=False
        )

    return None

get_temp_dir(project_name, anatomy=None, prefix=None, suffix=None, use_local_temp=False)

Get temporary dir path.

If use_local_temp is set, tempdir will be created in local tempdir. If anatomy is not set, default anatomy will be used. If prefix or suffix is not set, default values will be used.

It also supports AYON_TMPDIR, so studio can define own temp shared repository per project or even per more granular context. Template formatting is supported also with optional keys. Folder is created in case it doesn't exists.

Parameters:

Name Type Description Default
project_name str

Name of project.

required
anatomy Optional[Anatomy]

Project Anatomy object.

None
suffix Optional[str]

Suffix for tempdir.

None
prefix Optional[str]

Prefix for tempdir.

None
use_local_temp Optional[bool]

If True, temp dir will be created in local tempdir.

False

Returns:

Name Type Description
str

Path to staging dir of instance.

Source code in client/ayon_core/pipeline/tempdir.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def get_temp_dir(
    project_name, anatomy=None, prefix=None, suffix=None, use_local_temp=False
):
    """Get temporary dir path.

    If `use_local_temp` is set, tempdir will be created in local tempdir.
    If `anatomy` is not set, default anatomy will be used.
    If `prefix` or `suffix` is not set, default values will be used.

    It also supports `AYON_TMPDIR`, so studio can define own temp
    shared repository per project or even per more granular context.
    Template formatting is supported also with optional keys. Folder is
    created in case it doesn't exists.

    Args:
        project_name (str): Name of project.
        anatomy (Optional[Anatomy]): Project Anatomy object.
        suffix (Optional[str]): Suffix for tempdir.
        prefix (Optional[str]): Prefix for tempdir.
        use_local_temp (Optional[bool]): If True, temp dir will be created in
            local tempdir.

    Returns:
        str: Path to staging dir of instance.

    """
    if prefix is None:
        prefix = "ay_tmp_"
    suffix = suffix or ""

    if use_local_temp:
        return _create_local_staging_dir(prefix, suffix)

    # make sure anatomy is set
    if not anatomy:
        anatomy = Anatomy(project_name)

    # get customized tempdir path from `OPENPYPE_TMPDIR` env var
    custom_temp_dir = _create_custom_tempdir(anatomy.project_name, anatomy)

    return _create_local_staging_dir(prefix, suffix, dirpath=custom_temp_dir)

install_ayon_plugins(project_name=None, host_name=None)

Install AYON core plugins and make sure the core is initialized.

Parameters:

Name Type Description Default
project_name Optional[str]

Name of project to install plugins for.

None
host_name Optional[str]

Name of host to install plugins for.

None
Source code in client/ayon_core/pipeline/context_tools.py
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
def install_ayon_plugins(project_name=None, host_name=None):
    """Install AYON core plugins and make sure the core is initialized.

    Args:
        project_name (Optional[str]): Name of project to install plugins for.
        host_name (Optional[str]): Name of host to install plugins for.

    """
    # Make sure global AYON connection has set site id and version
    # - this is necessary if 'install_host' is not called
    initialize_ayon_connection()
    # Make sure addons are loaded
    load_addons()

    log.info("Registering global plug-ins..")
    pyblish.api.register_plugin_path(PUBLISH_PATH)
    pyblish.api.register_discovery_filter(filter_pyblish_plugins)
    register_loader_plugin_path(LOAD_PATH)
    register_inventory_action_path(INVENTORY_PATH)

    if host_name is None:
        host_name = os.environ.get("AYON_HOST_NAME")

    addons_manager = _get_addons_manager()
    publish_plugin_dirs = addons_manager.collect_publish_plugin_paths(
        host_name)
    for path in publish_plugin_dirs:
        pyblish.api.register_plugin_path(path)

    create_plugin_paths = addons_manager.collect_create_plugin_paths(
        host_name)
    for path in create_plugin_paths:
        register_creator_plugin_path(path)

    load_plugin_paths = addons_manager.collect_load_plugin_paths(
        host_name)
    for path in load_plugin_paths:
        register_loader_plugin_path(path)

    inventory_action_paths = addons_manager.collect_inventory_action_paths(
        host_name)
    for path in inventory_action_paths:
        register_inventory_action_path(path)

    if project_name is None:
        project_name = os.environ.get("AYON_PROJECT_NAME")

    # Register studio specific plugins
    if project_name:
        anatomy = Anatomy(project_name)
        anatomy.set_root_environments()
        register_root(anatomy.roots)

        project_settings = get_project_settings(project_name)
        platform_name = platform.system().lower()
        project_plugins = (
            project_settings
            ["core"]
            ["project_plugins"]
            .get(platform_name)
        ) or []
        for path in project_plugins:
            try:
                path = str(path.format(**os.environ))
            except KeyError:
                pass

            if not path or not os.path.exists(path):
                continue

            pyblish.api.register_plugin_path(path)
            register_loader_plugin_path(path)
            register_creator_plugin_path(path)
            register_inventory_action_path(path)

install_host(host)

Install host into the running Python session.

Parameters:

Name Type Description Default
host HostBase

A host interface object.

required
Source code in client/ayon_core/pipeline/context_tools.py
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
def install_host(host):
    """Install `host` into the running Python session.

    Args:
        host (HostBase): A host interface object.

    """
    global _is_installed

    _is_installed = True

    # Make sure global AYON connection has set site id and version
    initialize_ayon_connection()

    addons_manager = _get_addons_manager()

    project_name = os.getenv("AYON_PROJECT_NAME")
    # WARNING: This might be an issue
    #   - commented out because 'traypublisher' does not have set project
    # if not project_name:
    #     raise ValueError(
    #         "AYON_PROJECT_NAME is missing in environment variables."
    #     )

    log.info("Activating {}..".format(project_name))

    # Optional host install function
    if hasattr(host, "install"):
        host.install()

    register_host(host)

    def modified_emit(obj, record):
        """Method replacing `emit` in Pyblish's MessageHandler."""
        try:
            record.msg = record.getMessage()
        except Exception:
            record.msg = str(record.msg)
        obj.records.append(record)

    MessageHandler.emit = modified_emit

    if os.environ.get("AYON_REMOTE_PUBLISH"):
        # target "farm" == rendering on farm, expects AYON_PUBLISH_DATA
        # target "remote" == remote execution, installs host
        print("Registering pyblish target: remote")
        pyblish.api.register_target("remote")
    else:
        pyblish.api.register_target("local")

    if is_in_tests():
        print("Registering pyblish target: automated")
        pyblish.api.register_target("automated")

    host_name = os.environ.get("AYON_HOST_NAME")

    # Give option to handle host installation
    for addon in addons_manager.get_enabled_addons():
        addon.on_host_install(host, host_name, project_name)

    install_ayon_plugins(project_name, host_name)

is_installed()

Return state of installation

Returns:

Type Description

True if installed, False otherwise

Source code in client/ayon_core/pipeline/context_tools.py
261
262
263
264
265
266
267
268
269
def is_installed():
    """Return state of installation

    Returns:
        True if installed, False otherwise

    """

    return _is_installed

load_container(Loader, representation, namespace=None, name=None, options=None, **kwargs)

Use Loader to load a representation.

Parameters:

Name Type Description Default
Loader Loader

The loader class to trigger.

required
representation str or dict

The representation id or full representation as returned by the database.

required
namespace (str, Optional)

The namespace to assign. Defaults to None.

None
name (str, Optional)

The name to assign. Defaults to product name.

None
options (dict, Optional)

Additional options to pass on to the loader.

None

Returns:

Type Description

The return of the loader.load() method.

Raises:

Type Description
IncompatibleLoaderError

When the loader is not compatible with the representation.

Source code in client/ayon_core/pipeline/load/utils.py
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
def load_container(
    Loader, representation, namespace=None, name=None, options=None, **kwargs
):
    """Use Loader to load a representation.

    Args:
        Loader (Loader): The loader class to trigger.
        representation (str or dict): The representation id
            or full representation as returned by the database.
        namespace (str, Optional): The namespace to assign. Defaults to None.
        name (str, Optional): The name to assign. Defaults to product name.
        options (dict, Optional): Additional options to pass on to the loader.

    Returns:
        The return of the `loader.load()` method.

    Raises:
        IncompatibleLoaderError: When the loader is not compatible with
            the representation.

    """
    from ayon_core.pipeline import get_current_project_name

    context = get_representation_context(
        get_current_project_name(), representation
    )
    return load_with_repre_context(
        Loader,
        context,
        namespace=namespace,
        name=name,
        options=options,
        **kwargs
    )

loaders_from_representation(loaders, representation)

Return all compatible loaders for a representation.

Source code in client/ayon_core/pipeline/load/utils.py
910
911
912
913
914
915
916
917
918
def loaders_from_representation(loaders, representation):
    """Return all compatible loaders for a representation."""
    from ayon_core.pipeline import get_current_project_name

    project_name = get_current_project_name()
    context = get_representation_context(
        project_name, representation
    )
    return loaders_from_repre_context(loaders, context)

register_host(host)

Register a new host for the current process

Parameters:

Name Type Description Default
host ModuleType

A module implementing the Host API interface. See the Host API documentation for details on what is required, or browse the source code.

required
Source code in client/ayon_core/pipeline/context_tools.py
272
273
274
275
276
277
278
279
280
281
282
283
def register_host(host):
    """Register a new host for the current process

    Arguments:
        host (ModuleType): A module implementing the
            Host API interface. See the Host API
            documentation for details on what is
            required, or browse the source code.

    """

    _registered_host["_"] = host

register_root(path)

Register currently active root

Source code in client/ayon_core/pipeline/context_tools.py
76
77
78
79
def register_root(path):
    """Register currently active root"""
    log.info("Registering root: %s" % path)
    _registered_root["_"] = path

registered_host()

Return currently registered host

Source code in client/ayon_core/pipeline/context_tools.py
286
287
288
def registered_host():
    """Return currently registered host"""
    return _registered_host["_"]

registered_root()

Return registered roots from current project anatomy.

Consider this does return roots only for current project and current platforms, only if host was installer using 'install_host'.

Deprecated

Please use project 'Anatomy' to get roots. This function is still used at current core functions of load logic, but that will change in future and this function will be removed eventually. Using this function at new places can cause problems in the future.

Returns:

Type Description

dict[str, str]: Root paths.

Source code in client/ayon_core/pipeline/context_tools.py
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def registered_root():
    """Return registered roots from current project anatomy.

    Consider this does return roots only for current project and current
        platforms, only if host was installer using 'install_host'.

    Deprecated:
        Please use project 'Anatomy' to get roots. This function is still used
            at current core functions of load logic, but that will change
            in future and this function will be removed eventually. Using this
            function at new places can cause problems in the future.

    Returns:
        dict[str, str]: Root paths.
    """

    return _registered_root["_"]

remove_container(container)

Remove a container

Source code in client/ayon_core/pipeline/load/utils.py
450
451
452
453
454
455
456
457
458
459
460
def remove_container(container):
    """Remove a container"""

    Loader = _get_container_loader(container)
    if not Loader:
        raise LoaderNotFoundError(
            "Can't remove container because loader '{}' was not found."
            .format(container.get("loader"))
        )

    return Loader().remove(container)

switch_container(container, representation, loader_plugin=None)

Switch a container to representation

Parameters:

Name Type Description Default
container dict

container information

required
representation dict

representation entity

required

Returns:

Type Description

function call

Source code in client/ayon_core/pipeline/load/utils.py
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
def switch_container(container, representation, loader_plugin=None):
    """Switch a container to representation

    Args:
        container (dict): container information
        representation (dict): representation entity

    Returns:
        function call
    """
    from ayon_core.pipeline import get_current_project_name

    # Get the Loader for this container
    if loader_plugin is None:
        loader_plugin = _get_container_loader(container)

    if not loader_plugin:
        raise LoaderNotFoundError(
            "Can't switch container because loader '{}' was not found."
            .format(container.get("loader"))
        )

    if not hasattr(loader_plugin, "switch"):
        # Backwards compatibility (classes without switch support
        # might be better to just have "switch" raise NotImplementedError
        # on the base class of Loader\
        raise LoaderSwitchNotImplementedError(
            "Loader {} does not support 'switch'".format(loader_plugin.label)
        )

    # Get the new representation to switch to
    project_name = container.get("project_name")
    if project_name is None:
        project_name = get_current_project_name()

    context = get_representation_context(
        project_name, representation["id"]
    )
    if not is_compatible_loader(loader_plugin, context):
        raise IncompatibleLoaderError(
            "Loader {} is incompatible with {}".format(
                loader_plugin.__name__, context["product"]["name"]
            )
        )

    loader = loader_plugin(context)

    return loader.switch(container, context)

uninstall_host()

Undo all of what install() did

Source code in client/ayon_core/pipeline/context_tools.py
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
def uninstall_host():
    """Undo all of what `install()` did"""
    host = registered_host()

    try:
        host.uninstall()
    except AttributeError:
        pass

    log.info("Deregistering global plug-ins..")
    pyblish.api.deregister_plugin_path(PUBLISH_PATH)
    pyblish.api.deregister_discovery_filter(filter_pyblish_plugins)
    deregister_loader_plugin_path(LOAD_PATH)
    deregister_inventory_action_path(INVENTORY_PATH)
    log.info("Global plug-ins unregistred")

    deregister_host()

    log.info("Successfully uninstalled Avalon!")

update_container(container, version=-1)

Update a container

Source code in client/ayon_core/pipeline/load/utils.py
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
def update_container(container, version=-1):
    """Update a container"""
    from ayon_core.pipeline import get_current_project_name

    # Compute the different version from 'representation'
    project_name = container.get("project_name")
    if project_name is None:
        project_name = get_current_project_name()
    repre_id = container["representation"]
    if not _is_valid_representation_id(repre_id):
        raise ValueError(
            f"Got container with invalid representation id '{repre_id}'"
        )
    current_representation = ayon_api.get_representation_by_id(
        project_name, repre_id
    )

    assert current_representation is not None, "This is a bug"

    current_version_id = current_representation["versionId"]
    current_version = ayon_api.get_version_by_id(
        project_name, current_version_id, fields={"productId"}
    )
    if isinstance(version, HeroVersionType):
        new_version = ayon_api.get_hero_version_by_product_id(
            project_name, current_version["productId"]
        )
    elif version == -1:
        new_version = ayon_api.get_last_version_by_product_id(
            project_name, current_version["productId"]
        )

    else:
        new_version = ayon_api.get_version_by_name(
            project_name, version, current_version["productId"]
        )

    if new_version is None:
        raise ValueError("Failed to find matching version")

    product_entity = ayon_api.get_product_by_id(
        project_name, current_version["productId"]
    )
    folder_entity = ayon_api.get_folder_by_id(
        project_name, product_entity["folderId"]
    )

    # Run update on the Loader for this container
    Loader = _get_container_loader(container)
    if not Loader:
        raise LoaderNotFoundError(
            "Can't update container because loader '{}' was not found."
            .format(container.get("loader"))
        )

    repre_name = current_representation["name"]
    new_representation = ayon_api.get_representation_by_name(
        project_name, repre_name, new_version["id"]
    )
    if new_representation is None:
        # The representation name is not found in the new version.
        # Allow updating to a 'matching' representation if the loader
        # has defined compatible update conversions
        repre_name_aliases = Loader.get_representation_name_aliases(repre_name)
        if repre_name_aliases:
            representations = ayon_api.get_representations(
                project_name,
                representation_names=repre_name_aliases,
                version_ids=[new_version["id"]])
            representations_by_name = {
                repre["name"]: repre for repre in representations
            }
            for name in repre_name_aliases:
                if name in representations_by_name:
                    new_representation = representations_by_name[name]
                    break

        if new_representation is None:
            raise ValueError(
                "Representation '{}' wasn't found on requested version".format(
                    repre_name
                )
            )

    project_entity = ayon_api.get_project(project_name)
    context = {
        "project": project_entity,
        "folder": folder_entity,
        "product": product_entity,
        "version": new_version,
        "representation": new_representation,
    }
    path = get_representation_path_from_context(context)
    if not path or not os.path.exists(path):
        raise ValueError("Path {} doesn't exist".format(path))

    return Loader().update(container, context)