Skip to content

addon

AYONAddon

Bases: ABC

Base class of AYON addon.

Attributes:

Name Type Description
enabled bool

Is addon enabled.

name str

Addon name.

Parameters:

Name Type Description Default
manager AddonsManager

Manager object who discovered addon.

required
settings dict[str, Any]

AYON settings.

required
Source code in client/ayon_core/addon/base.py
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
class AYONAddon(ABC):
    """Base class of AYON addon.

    Attributes:
        enabled (bool): Is addon enabled.
        name (str): Addon name.

    Args:
        manager (AddonsManager): Manager object who discovered addon.
        settings (dict[str, Any]): AYON settings.

    """
    enabled = True
    _id = None

    # Temporary variable for 'version' property
    _missing_version_warned = False

    def __init__(self, manager, settings):
        self.manager = manager

        self.log = Logger.get_logger(self.name)

        self.initialize(settings)

    @property
    def id(self):
        """Random id of addon object.

        Returns:
            str: Object id.

        """
        if self._id is None:
            self._id = uuid4()
        return self._id

    @property
    @abstractmethod
    def name(self):
        """Addon name.

        Returns:
            str: Addon name.

        """
        pass

    @property
    def version(self):
        """Addon version.

        Todo:
            Should be abstract property (required). Introduced in
                ayon-core 0.3.3 .

        Returns:
            str: Addon version as semver compatible string.

        """
        if not self.__class__._missing_version_warned:
            self.__class__._missing_version_warned = True
            print(
                f"DEV WARNING: Addon '{self.name}' does not have"
                f" defined version."
            )
        return "0.0.0"

    def initialize(self, settings):
        """Initialization of addon attributes.

        It is not recommended to override __init__ that's why specific method
        was implemented.

        Args:
            settings (dict[str, Any]): Settings.

        """
        pass

    def connect_with_addons(self, enabled_addons):
        """Connect with other enabled addons.

        Args:
            enabled_addons (list[AYONAddon]): Addons that are enabled.

        """
        pass

    def ensure_is_process_ready(
        self, process_context: ProcessContext
    ):
        """Make sure addon is prepared for a process.

        This method is called when some action makes sure that addon has set
        necessary data. For example if user should be logged in
        and filled credentials in environment variables this method should
        ask user for credentials.

        Implementation of this method is optional.

        Note:
            The logic can be similar to logic in tray, but tray does not
                require to be logged in.

        Args:
            process_context (ProcessContext): Context of child
                process.

        """
        pass

    def get_global_environments(self):
        """Get global environments values of addon.

        Environment variables that can be get only from system settings.

        Returns:
            dict[str, str]: Environment variables.

        """
        return {}

    def modify_application_launch_arguments(self, application, env):
        """Give option to modify launch environments before application launch.

        Implementation is optional. To change environments modify passed
        dictionary of environments.

        Args:
            application (Application): Application that is launched.
            env (dict[str, str]): Current environment variables.

        """
        pass

    def on_host_install(self, host, host_name, project_name):
        """Host was installed which gives option to handle in-host logic.

        It is a good option to register in-host event callbacks which are
        specific for the addon. The addon is kept in memory for rest of
        the process.

        Arguments may change in future. E.g. 'host_name' should be possible
        to receive from 'host' object.

        Args:
            host (Union[ModuleType, HostBase]): Access to installed/registered
                host object.
            host_name (str): Name of host.
            project_name (str): Project name which is main part of host
                context.

        """
        pass

    def cli(self, addon_click_group):
        """Add commands to click group.

        The best practise is to create click group for whole addon which is
        used to separate commands.

        Example:
            class MyPlugin(AYONAddon):
                ...
                def cli(self, addon_click_group):
                    addon_click_group.add_command(cli_main)


            @click.group(<addon name>, help="<Any help shown in cmd>")
            def cli_main():
                pass

            @cli_main.command()
            def mycommand():
                print("my_command")

        Args:
            addon_click_group (click.Group): Group to which can be added
                commands.

        """
        pass

id property

Random id of addon object.

Returns:

Name Type Description
str

Object id.

name abstractmethod property

Addon name.

Returns:

Name Type Description
str

Addon name.

version property

Addon version.

Todo

Should be abstract property (required). Introduced in ayon-core 0.3.3 .

Returns:

Name Type Description
str

Addon version as semver compatible string.

cli(addon_click_group)

Add commands to click group.

The best practise is to create click group for whole addon which is used to separate commands.

Example

class MyPlugin(AYONAddon): ... def cli(self, addon_click_group): addon_click_group.add_command(cli_main)

@click.group(, help="") def cli_main(): pass

@cli_main.command() def mycommand(): print("my_command")

Parameters:

Name Type Description Default
addon_click_group Group

Group to which can be added commands.

required
Source code in client/ayon_core/addon/base.py
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
def cli(self, addon_click_group):
    """Add commands to click group.

    The best practise is to create click group for whole addon which is
    used to separate commands.

    Example:
        class MyPlugin(AYONAddon):
            ...
            def cli(self, addon_click_group):
                addon_click_group.add_command(cli_main)


        @click.group(<addon name>, help="<Any help shown in cmd>")
        def cli_main():
            pass

        @cli_main.command()
        def mycommand():
            print("my_command")

    Args:
        addon_click_group (click.Group): Group to which can be added
            commands.

    """
    pass

connect_with_addons(enabled_addons)

Connect with other enabled addons.

Parameters:

Name Type Description Default
enabled_addons list[AYONAddon]

Addons that are enabled.

required
Source code in client/ayon_core/addon/base.py
460
461
462
463
464
465
466
467
def connect_with_addons(self, enabled_addons):
    """Connect with other enabled addons.

    Args:
        enabled_addons (list[AYONAddon]): Addons that are enabled.

    """
    pass

ensure_is_process_ready(process_context)

Make sure addon is prepared for a process.

This method is called when some action makes sure that addon has set necessary data. For example if user should be logged in and filled credentials in environment variables this method should ask user for credentials.

Implementation of this method is optional.

Note

The logic can be similar to logic in tray, but tray does not require to be logged in.

Parameters:

Name Type Description Default
process_context ProcessContext

Context of child process.

required
Source code in client/ayon_core/addon/base.py
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
def ensure_is_process_ready(
    self, process_context: ProcessContext
):
    """Make sure addon is prepared for a process.

    This method is called when some action makes sure that addon has set
    necessary data. For example if user should be logged in
    and filled credentials in environment variables this method should
    ask user for credentials.

    Implementation of this method is optional.

    Note:
        The logic can be similar to logic in tray, but tray does not
            require to be logged in.

    Args:
        process_context (ProcessContext): Context of child
            process.

    """
    pass

get_global_environments()

Get global environments values of addon.

Environment variables that can be get only from system settings.

Returns:

Type Description

dict[str, str]: Environment variables.

Source code in client/ayon_core/addon/base.py
492
493
494
495
496
497
498
499
500
501
def get_global_environments(self):
    """Get global environments values of addon.

    Environment variables that can be get only from system settings.

    Returns:
        dict[str, str]: Environment variables.

    """
    return {}

initialize(settings)

Initialization of addon attributes.

It is not recommended to override init that's why specific method was implemented.

Parameters:

Name Type Description Default
settings dict[str, Any]

Settings.

required
Source code in client/ayon_core/addon/base.py
448
449
450
451
452
453
454
455
456
457
458
def initialize(self, settings):
    """Initialization of addon attributes.

    It is not recommended to override __init__ that's why specific method
    was implemented.

    Args:
        settings (dict[str, Any]): Settings.

    """
    pass

modify_application_launch_arguments(application, env)

Give option to modify launch environments before application launch.

Implementation is optional. To change environments modify passed dictionary of environments.

Parameters:

Name Type Description Default
application Application

Application that is launched.

required
env dict[str, str]

Current environment variables.

required
Source code in client/ayon_core/addon/base.py
503
504
505
506
507
508
509
510
511
512
513
514
def modify_application_launch_arguments(self, application, env):
    """Give option to modify launch environments before application launch.

    Implementation is optional. To change environments modify passed
    dictionary of environments.

    Args:
        application (Application): Application that is launched.
        env (dict[str, str]): Current environment variables.

    """
    pass

on_host_install(host, host_name, project_name)

Host was installed which gives option to handle in-host logic.

It is a good option to register in-host event callbacks which are specific for the addon. The addon is kept in memory for rest of the process.

Arguments may change in future. E.g. 'host_name' should be possible to receive from 'host' object.

Parameters:

Name Type Description Default
host Union[ModuleType, HostBase]

Access to installed/registered host object.

required
host_name str

Name of host.

required
project_name str

Project name which is main part of host context.

required
Source code in client/ayon_core/addon/base.py
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
def on_host_install(self, host, host_name, project_name):
    """Host was installed which gives option to handle in-host logic.

    It is a good option to register in-host event callbacks which are
    specific for the addon. The addon is kept in memory for rest of
    the process.

    Arguments may change in future. E.g. 'host_name' should be possible
    to receive from 'host' object.

    Args:
        host (Union[ModuleType, HostBase]): Access to installed/registered
            host object.
        host_name (str): Name of host.
        project_name (str): Project name which is main part of host
            context.

    """
    pass

AddonsManager

Manager of addons that helps to load and prepare them to work.

Parameters:

Name Type Description Default
settings Optional[dict[str, Any]]

AYON studio settings.

None
initialize Optional[bool]

Initialize addons on init. True by default.

True
Source code in client/ayon_core/addon/base.py
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
class AddonsManager:
    """Manager of addons that helps to load and prepare them to work.

    Args:
        settings (Optional[dict[str, Any]]): AYON studio settings.
        initialize (Optional[bool]): Initialize addons on init.
            True by default.

    """
    # Helper attributes for report
    _report_total_key = "Total"
    _log = None

    def __init__(self, settings=None, initialize=True):
        self._settings = settings

        self._addons = []
        self._addons_by_id = {}
        self._addons_by_name = {}
        # For report of time consumption
        self._report = {}

        if initialize:
            self.initialize_addons()
            self.connect_addons()

    def __getitem__(self, addon_name):
        return self._addons_by_name[addon_name]

    @property
    def log(self):
        if self._log is None:
            self._log = logging.getLogger(self.__class__.__name__)
        return self._log

    def get(self, addon_name, default=None):
        """Access addon by name.

        Args:
            addon_name (str): Name of addon which should be returned.
            default (Any): Default output if addon is not available.

        Returns:
            Union[AYONAddon, Any]: Addon found by name or `default`.

        """
        return self._addons_by_name.get(addon_name, default)

    @property
    def addons(self):
        return list(self._addons)

    @property
    def addons_by_id(self):
        return dict(self._addons_by_id)

    @property
    def addons_by_name(self):
        return dict(self._addons_by_name)

    def get_enabled_addon(self, addon_name, default=None):
        """Fast access to enabled addon.

        If addon is available but is not enabled default value is returned.

        Args:
            addon_name (str): Name of addon which should be returned.
            default (Any): Default output if addon is not available or is
                not enabled.

        Returns:
            Union[AYONAddon, None]: Enabled addon found by name or None.

        """
        addon = self.get(addon_name)
        if addon is not None and addon.enabled:
            return addon
        return default

    def get_enabled_addons(self):
        """Enabled addons initialized by the manager.

        Returns:
            list[AYONAddon]: Initialized and enabled addons.

        """
        return [
            addon
            for addon in self._addons
            if addon.enabled
        ]

    def initialize_addons(self):
        """Import and initialize addons."""
        # Make sure modules are loaded
        load_addons()

        self.log.debug("*** AYON addons initialization.")

        # Prepare settings for addons
        settings = self._settings
        if settings is None:
            settings = get_studio_settings()

        report = {}
        time_start = time.time()
        prev_start_time = time_start

        addon_classes = []
        for module in _LoadCache.addon_modules:
            # Go through globals in `ayon_core.modules`
            for name in dir(module):
                modules_item = getattr(module, name, None)
                # Filter globals that are not classes which inherit from
                #   AYONAddon
                if (
                    not inspect.isclass(modules_item)
                    or modules_item is AYONAddon
                    or not issubclass(modules_item, AYONAddon)
                ):
                    continue

                # Check if class is abstract (Developing purpose)
                if inspect.isabstract(modules_item):
                    # Find abstract attributes by convention on `abc` module
                    not_implemented = []
                    for attr_name in dir(modules_item):
                        attr = getattr(modules_item, attr_name, None)
                        abs_method = getattr(
                            attr, "__isabstractmethod__", None
                        )
                        if attr and abs_method:
                            not_implemented.append(attr_name)

                    # Log missing implementations
                    self.log.warning((
                        "Skipping abstract Class: {}."
                        " Missing implementations: {}"
                    ).format(name, ", ".join(not_implemented)))
                    continue

                addon_classes.append(modules_item)

        for addon_cls in addon_classes:
            name = addon_cls.__name__
            try:
                addon = addon_cls(self, settings)
                # Store initialized object
                self._addons.append(addon)
                self._addons_by_id[addon.id] = addon
                self._addons_by_name[addon.name] = addon

                now = time.time()
                report[addon.__class__.__name__] = now - prev_start_time
                prev_start_time = now

            except Exception:
                self.log.warning(
                    "Initialization of addon '{}' failed.".format(name),
                    exc_info=True
                )

        for addon_name in sorted(self._addons_by_name.keys()):
            addon = self._addons_by_name[addon_name]
            enabled_str = "X" if addon.enabled else " "
            self.log.debug(
                f"[{enabled_str}] {addon.name} ({addon.version})"
            )

        if self._report is not None:
            report[self._report_total_key] = time.time() - time_start
            self._report["Initialization"] = report

    def connect_addons(self):
        """Trigger connection with other enabled addons.

        Addons should handle their interfaces in `connect_with_addons`.
        """
        report = {}
        time_start = time.time()
        prev_start_time = time_start
        enabled_addons = self.get_enabled_addons()
        self.log.debug("Has {} enabled addons.".format(len(enabled_addons)))
        for addon in enabled_addons:
            try:
                addon.connect_with_addons(enabled_addons)

            except Exception:
                self.log.error(
                    "BUG: Module failed on connection with other modules.",
                    exc_info=True
                )

            now = time.time()
            report[addon.__class__.__name__] = now - prev_start_time
            prev_start_time = now

        if self._report is not None:
            report[self._report_total_key] = time.time() - time_start
            self._report["Connect modules"] = report

    def collect_global_environments(self):
        """Helper to collect global environment variabled from modules.

        Returns:
            dict: Global environment variables from enabled modules.

        Raises:
            AssertionError: Global environment variables must be unique for
                all modules.
        """
        module_envs = {}
        for module in self.get_enabled_addons():
            # Collect global module's global environments
            _envs = module.get_global_environments()
            for key, value in _envs.items():
                if key in module_envs:
                    # TODO better error message
                    raise AssertionError(
                        "Duplicated environment key {}".format(key)
                    )
                module_envs[key] = value
        return module_envs

    def collect_plugin_paths(self):
        """Helper to collect all plugins from modules inherited IPluginPaths.

        Unknown keys are logged out.

        Returns:
            dict: Output is dictionary with keys "publish", "create", "load",
                "actions" and "inventory" each containing list of paths.
        """
        # Output structure
        output = {
            "publish": [],
            "create": [],
            "load": [],
            "actions": [],
            "inventory": []
        }
        unknown_keys_by_addon = {}
        for addon in self.get_enabled_addons():
            # Skip module that do not inherit from `IPluginPaths`
            if not isinstance(addon, IPluginPaths):
                continue
            plugin_paths = addon.get_plugin_paths()
            for key, value in plugin_paths.items():
                # Filter unknown keys
                if key not in output:
                    if addon.name not in unknown_keys_by_addon:
                        unknown_keys_by_addon[addon.name] = []
                    unknown_keys_by_addon[addon.name].append(key)
                    continue

                # Skip if value is empty
                if not value:
                    continue

                # Convert to list if value is not list
                if not isinstance(value, (list, tuple, set)):
                    value = [value]
                output[key].extend(value)

        # Report unknown keys (Developing purposes)
        if unknown_keys_by_addon:
            expected_keys = ", ".join([
                "\"{}\"".format(key) for key in output.keys()
            ])
            msg_template = "Addon: \"{}\" - got key {}"
            msg_items = []
            for addon_name, keys in unknown_keys_by_addon.items():
                joined_keys = ", ".join([
                    "\"{}\"".format(key) for key in keys
                ])
                msg_items.append(msg_template.format(addon_name, joined_keys))
            self.log.warning((
                "Expected keys from `get_plugin_paths` are {}. {}"
            ).format(expected_keys, " | ".join(msg_items)))
        return output

    def _collect_plugin_paths(self, method_name, *args, **kwargs):
        output = []
        for addon in self.get_enabled_addons():
            # Skip addon that do not inherit from `IPluginPaths`
            if not isinstance(addon, IPluginPaths):
                continue

            method = getattr(addon, method_name)
            try:
                paths = method(*args, **kwargs)
            except Exception:
                self.log.warning(
                    (
                        "Failed to get plugin paths from addon"
                        " '{}' using '{}'."
                    ).format(addon.__class__.__name__, method_name),
                    exc_info=True
                )
                continue

            if paths:
                # Convert to list if value is not list
                if not isinstance(paths, (list, tuple, set)):
                    paths = [paths]
                output.extend(paths)
        return output

    def collect_launcher_action_paths(self):
        """Helper to collect launcher action paths from addons.

        Returns:
            list: List of paths to launcher actions.

        """
        output = self._collect_plugin_paths(
            "get_launcher_action_paths"
        )
        # Add default core actions
        actions_dir = os.path.join(AYON_CORE_ROOT, "plugins", "actions")
        output.insert(0, actions_dir)
        return output

    def collect_create_plugin_paths(self, host_name):
        """Helper to collect creator plugin paths from addons.

        Args:
            host_name (str): For which host are creators meant.

        Returns:
            list: List of creator plugin paths.
        """

        return self._collect_plugin_paths(
            "get_create_plugin_paths",
            host_name
        )

    collect_creator_plugin_paths = collect_create_plugin_paths

    def collect_load_plugin_paths(self, host_name):
        """Helper to collect load plugin paths from addons.

        Args:
            host_name (str): For which host are load plugins meant.

        Returns:
            list: List of load plugin paths.
        """

        return self._collect_plugin_paths(
            "get_load_plugin_paths",
            host_name
        )

    def collect_publish_plugin_paths(self, host_name):
        """Helper to collect load plugin paths from addons.

        Args:
            host_name (str): For which host are load plugins meant.

        Returns:
            list: List of pyblish plugin paths.
        """

        return self._collect_plugin_paths(
            "get_publish_plugin_paths",
            host_name
        )

    def collect_inventory_action_paths(self, host_name):
        """Helper to collect load plugin paths from addons.

        Args:
            host_name (str): For which host are load plugins meant.

        Returns:
            list: List of pyblish plugin paths.
        """

        return self._collect_plugin_paths(
            "get_inventory_action_paths",
            host_name
        )

    def get_host_addon(self, host_name):
        """Find host addon by host name.

        Args:
            host_name (str): Host name for which is found host addon.

        Returns:
            Union[AYONAddon, None]: Found host addon by name or `None`.
        """

        for addon in self.get_enabled_addons():
            if (
                isinstance(addon, IHostAddon)
                and addon.host_name == host_name
            ):
                return addon
        return None

    def get_host_names(self):
        """List of available host names based on host addons.

        Returns:
            Iterable[str]: All available host names based on enabled addons
                inheriting 'IHostAddon'.
        """

        return {
            addon.host_name
            for addon in self.get_enabled_addons()
            if isinstance(addon, IHostAddon)
        }

    def print_report(self):
        """Print out report of time spent on addons initialization parts.

        Reporting is not automated must be implemented for each initialization
        part separately. Reports must be stored to `_report` attribute.
        Print is skipped if `_report` is empty.

        Attribute `_report` is dictionary where key is "label" describing
        the processed part and value is dictionary where key is addon's
        class name and value is time delta of it's processing.

        It is good idea to add total time delta on processed part under key
        which is defined in attribute `_report_total_key`. By default has value
        `"Total"` but use the attribute please.

        ```javascript
        {
            "Initialization": {
                "FtrackAddon": 0.003,
                ...
                "Total": 1.003,
            },
            ...
        }
        ```
        """

        if not self._report:
            return

        available_col_names = set()
        for addon_names in self._report.values():
            available_col_names |= set(addon_names.keys())

        # Prepare ordered dictionary for columns
        addons_info = [
            _AddonReportInfo.from_addon(addon, self._report)
            for addon in self.addons
            if addon.__class__.__name__ in available_col_names
        ]
        addons_info.sort(key=lambda x: x.name)

        addon_name_rows = [
            addon_info.name
            for addon_info in addons_info
        ]
        addon_version_rows = [
            addon_info.version
            for addon_info in addons_info
        ]

        # Add total key (as last addon)
        addon_name_rows.append(self._report_total_key)
        addon_version_rows.append(f"({len(addons_info)})")

        cols = collections.OrderedDict()
        # Add addon names to first columnt
        cols["Addon name"] = addon_name_rows
        cols["Version"] = addon_version_rows

        # Add columns from report
        total_by_addon = {
            row: 0
            for row in addon_name_rows
        }
        for label in self._report.keys():
            rows = []
            col_total = 0
            for addon_info in addons_info:
                value = addon_info.report_value_by_label.get(label)
                if value is None:
                    rows.append("N/A")
                    continue
                rows.append("{:.3f}".format(value))
                total_by_addon[addon_info.name] += value
                col_total += value
            total_by_addon[self._report_total_key] += col_total
            rows.append("{:.3f}".format(col_total))
            cols[label] = rows
        # Add to also total column that should sum the row
        cols[self._report_total_key] = [
            "{:.3f}".format(total_by_addon[addon_name])
            for addon_name in cols["Addon name"]
        ]

        # Prepare column widths and total row count
        # - column width is by
        col_widths = {}
        total_rows = None
        for key, values in cols.items():
            if total_rows is None:
                total_rows = 1 + len(values)
            max_width = len(key)
            for value in values:
                value_length = len(value)
                if value_length > max_width:
                    max_width = value_length
            col_widths[key] = max_width

        rows = []
        for _idx in range(total_rows):
            rows.append([])

        for key, values in cols.items():
            width = col_widths[key]
            idx = 0
            rows[idx].append(key.ljust(width))
            for value in values:
                idx += 1
                rows[idx].append(value.ljust(width))

        filler_parts = []
        for width in col_widths.values():
            filler_parts.append(width * "-")
        filler = "+".join(filler_parts)

        formatted_rows = [filler]
        last_row_idx = len(rows) - 1
        for idx, row in enumerate(rows):
            # Add filler before last row
            if idx == last_row_idx:
                formatted_rows.append(filler)

            formatted_rows.append("|".join(row))

            # Add filler after first row
            if idx == 0:
                formatted_rows.append(filler)

        # Join rows with newline char and add new line at the end
        output = "\n".join(formatted_rows) + "\n"
        print(output)

collect_create_plugin_paths(host_name)

Helper to collect creator plugin paths from addons.

Parameters:

Name Type Description Default
host_name str

For which host are creators meant.

required

Returns:

Name Type Description
list

List of creator plugin paths.

Source code in client/ayon_core/addon/base.py
912
913
914
915
916
917
918
919
920
921
922
923
924
925
def collect_create_plugin_paths(self, host_name):
    """Helper to collect creator plugin paths from addons.

    Args:
        host_name (str): For which host are creators meant.

    Returns:
        list: List of creator plugin paths.
    """

    return self._collect_plugin_paths(
        "get_create_plugin_paths",
        host_name
    )

collect_global_environments()

Helper to collect global environment variabled from modules.

Returns:

Name Type Description
dict

Global environment variables from enabled modules.

Raises:

Type Description
AssertionError

Global environment variables must be unique for all modules.

Source code in client/ayon_core/addon/base.py
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
def collect_global_environments(self):
    """Helper to collect global environment variabled from modules.

    Returns:
        dict: Global environment variables from enabled modules.

    Raises:
        AssertionError: Global environment variables must be unique for
            all modules.
    """
    module_envs = {}
    for module in self.get_enabled_addons():
        # Collect global module's global environments
        _envs = module.get_global_environments()
        for key, value in _envs.items():
            if key in module_envs:
                # TODO better error message
                raise AssertionError(
                    "Duplicated environment key {}".format(key)
                )
            module_envs[key] = value
    return module_envs

collect_inventory_action_paths(host_name)

Helper to collect load plugin paths from addons.

Parameters:

Name Type Description Default
host_name str

For which host are load plugins meant.

required

Returns:

Name Type Description
list

List of pyblish plugin paths.

Source code in client/ayon_core/addon/base.py
959
960
961
962
963
964
965
966
967
968
969
970
971
972
def collect_inventory_action_paths(self, host_name):
    """Helper to collect load plugin paths from addons.

    Args:
        host_name (str): For which host are load plugins meant.

    Returns:
        list: List of pyblish plugin paths.
    """

    return self._collect_plugin_paths(
        "get_inventory_action_paths",
        host_name
    )

collect_launcher_action_paths()

Helper to collect launcher action paths from addons.

Returns:

Name Type Description
list

List of paths to launcher actions.

Source code in client/ayon_core/addon/base.py
897
898
899
900
901
902
903
904
905
906
907
908
909
910
def collect_launcher_action_paths(self):
    """Helper to collect launcher action paths from addons.

    Returns:
        list: List of paths to launcher actions.

    """
    output = self._collect_plugin_paths(
        "get_launcher_action_paths"
    )
    # Add default core actions
    actions_dir = os.path.join(AYON_CORE_ROOT, "plugins", "actions")
    output.insert(0, actions_dir)
    return output

collect_load_plugin_paths(host_name)

Helper to collect load plugin paths from addons.

Parameters:

Name Type Description Default
host_name str

For which host are load plugins meant.

required

Returns:

Name Type Description
list

List of load plugin paths.

Source code in client/ayon_core/addon/base.py
929
930
931
932
933
934
935
936
937
938
939
940
941
942
def collect_load_plugin_paths(self, host_name):
    """Helper to collect load plugin paths from addons.

    Args:
        host_name (str): For which host are load plugins meant.

    Returns:
        list: List of load plugin paths.
    """

    return self._collect_plugin_paths(
        "get_load_plugin_paths",
        host_name
    )

collect_plugin_paths()

Helper to collect all plugins from modules inherited IPluginPaths.

Unknown keys are logged out.

Returns:

Name Type Description
dict

Output is dictionary with keys "publish", "create", "load", "actions" and "inventory" each containing list of paths.

Source code in client/ayon_core/addon/base.py
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
def collect_plugin_paths(self):
    """Helper to collect all plugins from modules inherited IPluginPaths.

    Unknown keys are logged out.

    Returns:
        dict: Output is dictionary with keys "publish", "create", "load",
            "actions" and "inventory" each containing list of paths.
    """
    # Output structure
    output = {
        "publish": [],
        "create": [],
        "load": [],
        "actions": [],
        "inventory": []
    }
    unknown_keys_by_addon = {}
    for addon in self.get_enabled_addons():
        # Skip module that do not inherit from `IPluginPaths`
        if not isinstance(addon, IPluginPaths):
            continue
        plugin_paths = addon.get_plugin_paths()
        for key, value in plugin_paths.items():
            # Filter unknown keys
            if key not in output:
                if addon.name not in unknown_keys_by_addon:
                    unknown_keys_by_addon[addon.name] = []
                unknown_keys_by_addon[addon.name].append(key)
                continue

            # Skip if value is empty
            if not value:
                continue

            # Convert to list if value is not list
            if not isinstance(value, (list, tuple, set)):
                value = [value]
            output[key].extend(value)

    # Report unknown keys (Developing purposes)
    if unknown_keys_by_addon:
        expected_keys = ", ".join([
            "\"{}\"".format(key) for key in output.keys()
        ])
        msg_template = "Addon: \"{}\" - got key {}"
        msg_items = []
        for addon_name, keys in unknown_keys_by_addon.items():
            joined_keys = ", ".join([
                "\"{}\"".format(key) for key in keys
            ])
            msg_items.append(msg_template.format(addon_name, joined_keys))
        self.log.warning((
            "Expected keys from `get_plugin_paths` are {}. {}"
        ).format(expected_keys, " | ".join(msg_items)))
    return output

collect_publish_plugin_paths(host_name)

Helper to collect load plugin paths from addons.

Parameters:

Name Type Description Default
host_name str

For which host are load plugins meant.

required

Returns:

Name Type Description
list

List of pyblish plugin paths.

Source code in client/ayon_core/addon/base.py
944
945
946
947
948
949
950
951
952
953
954
955
956
957
def collect_publish_plugin_paths(self, host_name):
    """Helper to collect load plugin paths from addons.

    Args:
        host_name (str): For which host are load plugins meant.

    Returns:
        list: List of pyblish plugin paths.
    """

    return self._collect_plugin_paths(
        "get_publish_plugin_paths",
        host_name
    )

connect_addons()

Trigger connection with other enabled addons.

Addons should handle their interfaces in connect_with_addons.

Source code in client/ayon_core/addon/base.py
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
def connect_addons(self):
    """Trigger connection with other enabled addons.

    Addons should handle their interfaces in `connect_with_addons`.
    """
    report = {}
    time_start = time.time()
    prev_start_time = time_start
    enabled_addons = self.get_enabled_addons()
    self.log.debug("Has {} enabled addons.".format(len(enabled_addons)))
    for addon in enabled_addons:
        try:
            addon.connect_with_addons(enabled_addons)

        except Exception:
            self.log.error(
                "BUG: Module failed on connection with other modules.",
                exc_info=True
            )

        now = time.time()
        report[addon.__class__.__name__] = now - prev_start_time
        prev_start_time = now

    if self._report is not None:
        report[self._report_total_key] = time.time() - time_start
        self._report["Connect modules"] = report

get(addon_name, default=None)

Access addon by name.

Parameters:

Name Type Description Default
addon_name str

Name of addon which should be returned.

required
default Any

Default output if addon is not available.

None

Returns:

Type Description

Union[AYONAddon, Any]: Addon found by name or default.

Source code in client/ayon_core/addon/base.py
624
625
626
627
628
629
630
631
632
633
634
635
def get(self, addon_name, default=None):
    """Access addon by name.

    Args:
        addon_name (str): Name of addon which should be returned.
        default (Any): Default output if addon is not available.

    Returns:
        Union[AYONAddon, Any]: Addon found by name or `default`.

    """
    return self._addons_by_name.get(addon_name, default)

get_enabled_addon(addon_name, default=None)

Fast access to enabled addon.

If addon is available but is not enabled default value is returned.

Parameters:

Name Type Description Default
addon_name str

Name of addon which should be returned.

required
default Any

Default output if addon is not available or is not enabled.

None

Returns:

Type Description

Union[AYONAddon, None]: Enabled addon found by name or None.

Source code in client/ayon_core/addon/base.py
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
def get_enabled_addon(self, addon_name, default=None):
    """Fast access to enabled addon.

    If addon is available but is not enabled default value is returned.

    Args:
        addon_name (str): Name of addon which should be returned.
        default (Any): Default output if addon is not available or is
            not enabled.

    Returns:
        Union[AYONAddon, None]: Enabled addon found by name or None.

    """
    addon = self.get(addon_name)
    if addon is not None and addon.enabled:
        return addon
    return default

get_enabled_addons()

Enabled addons initialized by the manager.

Returns:

Type Description

list[AYONAddon]: Initialized and enabled addons.

Source code in client/ayon_core/addon/base.py
668
669
670
671
672
673
674
675
676
677
678
679
def get_enabled_addons(self):
    """Enabled addons initialized by the manager.

    Returns:
        list[AYONAddon]: Initialized and enabled addons.

    """
    return [
        addon
        for addon in self._addons
        if addon.enabled
    ]

get_host_addon(host_name)

Find host addon by host name.

Parameters:

Name Type Description Default
host_name str

Host name for which is found host addon.

required

Returns:

Type Description

Union[AYONAddon, None]: Found host addon by name or None.

Source code in client/ayon_core/addon/base.py
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
def get_host_addon(self, host_name):
    """Find host addon by host name.

    Args:
        host_name (str): Host name for which is found host addon.

    Returns:
        Union[AYONAddon, None]: Found host addon by name or `None`.
    """

    for addon in self.get_enabled_addons():
        if (
            isinstance(addon, IHostAddon)
            and addon.host_name == host_name
        ):
            return addon
    return None

get_host_names()

List of available host names based on host addons.

Returns:

Type Description

Iterable[str]: All available host names based on enabled addons inheriting 'IHostAddon'.

Source code in client/ayon_core/addon/base.py
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
def get_host_names(self):
    """List of available host names based on host addons.

    Returns:
        Iterable[str]: All available host names based on enabled addons
            inheriting 'IHostAddon'.
    """

    return {
        addon.host_name
        for addon in self.get_enabled_addons()
        if isinstance(addon, IHostAddon)
    }

initialize_addons()

Import and initialize addons.

Source code in client/ayon_core/addon/base.py
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
def initialize_addons(self):
    """Import and initialize addons."""
    # Make sure modules are loaded
    load_addons()

    self.log.debug("*** AYON addons initialization.")

    # Prepare settings for addons
    settings = self._settings
    if settings is None:
        settings = get_studio_settings()

    report = {}
    time_start = time.time()
    prev_start_time = time_start

    addon_classes = []
    for module in _LoadCache.addon_modules:
        # Go through globals in `ayon_core.modules`
        for name in dir(module):
            modules_item = getattr(module, name, None)
            # Filter globals that are not classes which inherit from
            #   AYONAddon
            if (
                not inspect.isclass(modules_item)
                or modules_item is AYONAddon
                or not issubclass(modules_item, AYONAddon)
            ):
                continue

            # Check if class is abstract (Developing purpose)
            if inspect.isabstract(modules_item):
                # Find abstract attributes by convention on `abc` module
                not_implemented = []
                for attr_name in dir(modules_item):
                    attr = getattr(modules_item, attr_name, None)
                    abs_method = getattr(
                        attr, "__isabstractmethod__", None
                    )
                    if attr and abs_method:
                        not_implemented.append(attr_name)

                # Log missing implementations
                self.log.warning((
                    "Skipping abstract Class: {}."
                    " Missing implementations: {}"
                ).format(name, ", ".join(not_implemented)))
                continue

            addon_classes.append(modules_item)

    for addon_cls in addon_classes:
        name = addon_cls.__name__
        try:
            addon = addon_cls(self, settings)
            # Store initialized object
            self._addons.append(addon)
            self._addons_by_id[addon.id] = addon
            self._addons_by_name[addon.name] = addon

            now = time.time()
            report[addon.__class__.__name__] = now - prev_start_time
            prev_start_time = now

        except Exception:
            self.log.warning(
                "Initialization of addon '{}' failed.".format(name),
                exc_info=True
            )

    for addon_name in sorted(self._addons_by_name.keys()):
        addon = self._addons_by_name[addon_name]
        enabled_str = "X" if addon.enabled else " "
        self.log.debug(
            f"[{enabled_str}] {addon.name} ({addon.version})"
        )

    if self._report is not None:
        report[self._report_total_key] = time.time() - time_start
        self._report["Initialization"] = report

print_report()

Print out report of time spent on addons initialization parts.

Reporting is not automated must be implemented for each initialization part separately. Reports must be stored to _report attribute. Print is skipped if _report is empty.

Attribute _report is dictionary where key is "label" describing the processed part and value is dictionary where key is addon's class name and value is time delta of it's processing.

It is good idea to add total time delta on processed part under key which is defined in attribute _report_total_key. By default has value "Total" but use the attribute please.

{
    "Initialization": {
        "FtrackAddon": 0.003,
        ...
        "Total": 1.003,
    },
    ...
}
Source code in client/ayon_core/addon/base.py
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
def print_report(self):
    """Print out report of time spent on addons initialization parts.

    Reporting is not automated must be implemented for each initialization
    part separately. Reports must be stored to `_report` attribute.
    Print is skipped if `_report` is empty.

    Attribute `_report` is dictionary where key is "label" describing
    the processed part and value is dictionary where key is addon's
    class name and value is time delta of it's processing.

    It is good idea to add total time delta on processed part under key
    which is defined in attribute `_report_total_key`. By default has value
    `"Total"` but use the attribute please.

    ```javascript
    {
        "Initialization": {
            "FtrackAddon": 0.003,
            ...
            "Total": 1.003,
        },
        ...
    }
    ```
    """

    if not self._report:
        return

    available_col_names = set()
    for addon_names in self._report.values():
        available_col_names |= set(addon_names.keys())

    # Prepare ordered dictionary for columns
    addons_info = [
        _AddonReportInfo.from_addon(addon, self._report)
        for addon in self.addons
        if addon.__class__.__name__ in available_col_names
    ]
    addons_info.sort(key=lambda x: x.name)

    addon_name_rows = [
        addon_info.name
        for addon_info in addons_info
    ]
    addon_version_rows = [
        addon_info.version
        for addon_info in addons_info
    ]

    # Add total key (as last addon)
    addon_name_rows.append(self._report_total_key)
    addon_version_rows.append(f"({len(addons_info)})")

    cols = collections.OrderedDict()
    # Add addon names to first columnt
    cols["Addon name"] = addon_name_rows
    cols["Version"] = addon_version_rows

    # Add columns from report
    total_by_addon = {
        row: 0
        for row in addon_name_rows
    }
    for label in self._report.keys():
        rows = []
        col_total = 0
        for addon_info in addons_info:
            value = addon_info.report_value_by_label.get(label)
            if value is None:
                rows.append("N/A")
                continue
            rows.append("{:.3f}".format(value))
            total_by_addon[addon_info.name] += value
            col_total += value
        total_by_addon[self._report_total_key] += col_total
        rows.append("{:.3f}".format(col_total))
        cols[label] = rows
    # Add to also total column that should sum the row
    cols[self._report_total_key] = [
        "{:.3f}".format(total_by_addon[addon_name])
        for addon_name in cols["Addon name"]
    ]

    # Prepare column widths and total row count
    # - column width is by
    col_widths = {}
    total_rows = None
    for key, values in cols.items():
        if total_rows is None:
            total_rows = 1 + len(values)
        max_width = len(key)
        for value in values:
            value_length = len(value)
            if value_length > max_width:
                max_width = value_length
        col_widths[key] = max_width

    rows = []
    for _idx in range(total_rows):
        rows.append([])

    for key, values in cols.items():
        width = col_widths[key]
        idx = 0
        rows[idx].append(key.ljust(width))
        for value in values:
            idx += 1
            rows[idx].append(value.ljust(width))

    filler_parts = []
    for width in col_widths.values():
        filler_parts.append(width * "-")
    filler = "+".join(filler_parts)

    formatted_rows = [filler]
    last_row_idx = len(rows) - 1
    for idx, row in enumerate(rows):
        # Add filler before last row
        if idx == last_row_idx:
            formatted_rows.append(filler)

        formatted_rows.append("|".join(row))

        # Add filler after first row
        if idx == 0:
            formatted_rows.append(filler)

    # Join rows with newline char and add new line at the end
    output = "\n".join(formatted_rows) + "\n"
    print(output)

IHostAddon

Bases: AYONInterface

Addon which also contain a host implementation.

Source code in client/ayon_core/addon/interfaces.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
class IHostAddon(AYONInterface):
    """Addon which also contain a host implementation."""

    @property
    @abstractmethod
    def host_name(self):
        """Name of host which addon represents."""

        pass

    def get_workfile_extensions(self):
        """Define workfile extensions for host.

        Not all hosts support workfiles thus this is optional implementation.

        Returns:
            List[str]: Extensions used for workfiles with dot.
        """

        return []

host_name abstractmethod property

Name of host which addon represents.

get_workfile_extensions()

Define workfile extensions for host.

Not all hosts support workfiles thus this is optional implementation.

Returns:

Type Description

List[str]: Extensions used for workfiles with dot.

Source code in client/ayon_core/addon/interfaces.py
386
387
388
389
390
391
392
393
394
395
def get_workfile_extensions(self):
    """Define workfile extensions for host.

    Not all hosts support workfiles thus this is optional implementation.

    Returns:
        List[str]: Extensions used for workfiles with dot.
    """

    return []

IPluginPaths

Bases: AYONInterface

Addon has plugin paths to return.

Expected result is dictionary with keys "publish", "create", "load", "actions" or "inventory" and values as list or string. { "publish": ["path/to/publish_plugins"] }

Source code in client/ayon_core/addon/interfaces.py
 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
class IPluginPaths(AYONInterface):
    """Addon has plugin paths to return.

    Expected result is dictionary with keys "publish", "create", "load",
    "actions" or "inventory" and values as list or string.
    {
        "publish": ["path/to/publish_plugins"]
    }
    """

    @abstractmethod
    def get_plugin_paths(self):
        pass

    def _get_plugin_paths_by_type(self, plugin_type):
        paths = self.get_plugin_paths()
        if not paths or plugin_type not in paths:
            return []

        paths = paths[plugin_type]
        if not paths:
            return []

        if not isinstance(paths, (list, tuple, set)):
            paths = [paths]
        return paths

    def get_launcher_action_paths(self):
        """Receive launcher actions paths.

        Give addons ability to add launcher actions paths.
        """
        return self._get_plugin_paths_by_type("actions")

    def get_create_plugin_paths(self, host_name):
        """Receive create plugin paths.

        Give addons ability to add create plugin paths based on host name.

        Notes:
            Default implementation uses 'get_plugin_paths' and always return
                all create plugin paths.

        Args:
            host_name (str): For which host are the plugins meant.
        """

        return self._get_plugin_paths_by_type("create")

    def get_load_plugin_paths(self, host_name):
        """Receive load plugin paths.

        Give addons ability to add load plugin paths based on host name.

        Notes:
            Default implementation uses 'get_plugin_paths' and always return
                all load plugin paths.

        Args:
            host_name (str): For which host are the plugins meant.
        """

        return self._get_plugin_paths_by_type("load")

    def get_publish_plugin_paths(self, host_name):
        """Receive publish plugin paths.

        Give addons ability to add publish plugin paths based on host name.

        Notes:
           Default implementation uses 'get_plugin_paths' and always return
               all publish plugin paths.

        Args:
           host_name (str): For which host are the plugins meant.
        """

        return self._get_plugin_paths_by_type("publish")

    def get_inventory_action_paths(self, host_name):
        """Receive inventory action paths.

        Give addons ability to add inventory action plugin paths.

        Notes:
           Default implementation uses 'get_plugin_paths' and always return
               all publish plugin paths.

        Args:
           host_name (str): For which host are the plugins meant.
        """

        return self._get_plugin_paths_by_type("inventory")

get_create_plugin_paths(host_name)

Receive create plugin paths.

Give addons ability to add create plugin paths based on host name.

Notes

Default implementation uses 'get_plugin_paths' and always return all create plugin paths.

Parameters:

Name Type Description Default
host_name str

For which host are the plugins meant.

required
Source code in client/ayon_core/addon/interfaces.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def get_create_plugin_paths(self, host_name):
    """Receive create plugin paths.

    Give addons ability to add create plugin paths based on host name.

    Notes:
        Default implementation uses 'get_plugin_paths' and always return
            all create plugin paths.

    Args:
        host_name (str): For which host are the plugins meant.
    """

    return self._get_plugin_paths_by_type("create")

get_inventory_action_paths(host_name)

Receive inventory action paths.

Give addons ability to add inventory action plugin paths.

Notes

Default implementation uses 'get_plugin_paths' and always return all publish plugin paths.

Parameters:

Name Type Description Default
host_name str

For which host are the plugins meant.

required
Source code in client/ayon_core/addon/interfaces.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def get_inventory_action_paths(self, host_name):
    """Receive inventory action paths.

    Give addons ability to add inventory action plugin paths.

    Notes:
       Default implementation uses 'get_plugin_paths' and always return
           all publish plugin paths.

    Args:
       host_name (str): For which host are the plugins meant.
    """

    return self._get_plugin_paths_by_type("inventory")

get_launcher_action_paths()

Receive launcher actions paths.

Give addons ability to add launcher actions paths.

Source code in client/ayon_core/addon/interfaces.py
57
58
59
60
61
62
def get_launcher_action_paths(self):
    """Receive launcher actions paths.

    Give addons ability to add launcher actions paths.
    """
    return self._get_plugin_paths_by_type("actions")

get_load_plugin_paths(host_name)

Receive load plugin paths.

Give addons ability to add load plugin paths based on host name.

Notes

Default implementation uses 'get_plugin_paths' and always return all load plugin paths.

Parameters:

Name Type Description Default
host_name str

For which host are the plugins meant.

required
Source code in client/ayon_core/addon/interfaces.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def get_load_plugin_paths(self, host_name):
    """Receive load plugin paths.

    Give addons ability to add load plugin paths based on host name.

    Notes:
        Default implementation uses 'get_plugin_paths' and always return
            all load plugin paths.

    Args:
        host_name (str): For which host are the plugins meant.
    """

    return self._get_plugin_paths_by_type("load")

get_publish_plugin_paths(host_name)

Receive publish plugin paths.

Give addons ability to add publish plugin paths based on host name.

Notes

Default implementation uses 'get_plugin_paths' and always return all publish plugin paths.

Parameters:

Name Type Description Default
host_name str

For which host are the plugins meant.

required
Source code in client/ayon_core/addon/interfaces.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def get_publish_plugin_paths(self, host_name):
    """Receive publish plugin paths.

    Give addons ability to add publish plugin paths based on host name.

    Notes:
       Default implementation uses 'get_plugin_paths' and always return
           all publish plugin paths.

    Args:
       host_name (str): For which host are the plugins meant.
    """

    return self._get_plugin_paths_by_type("publish")

ITrayAction

Bases: ITrayAddon

Implementation of Tray action.

Add action to tray menu which will trigger on_action_trigger. It is expected to be used for showing tools.

Methods tray_start, tray_exit and connect_with_addons are overridden as it's not expected that action will use them. But it is possible if necessary.

Source code in client/ayon_core/addon/interfaces.py
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
class ITrayAction(ITrayAddon):
    """Implementation of Tray action.

    Add action to tray menu which will trigger `on_action_trigger`.
    It is expected to be used for showing tools.

    Methods `tray_start`, `tray_exit` and `connect_with_addons` are overridden
    as it's not expected that action will use them. But it is possible if
    necessary.
    """

    admin_action = False
    _action_item = None

    @property
    @abstractmethod
    def label(self):
        """Service label showed in menu."""
        pass

    @abstractmethod
    def on_action_trigger(self):
        """What happens on actions click."""
        pass

    def tray_menu(self, tray_menu):
        from qtpy import QtWidgets

        if self.admin_action:
            action = self.add_action_to_admin_submenu(self.label, tray_menu)
        else:
            action = QtWidgets.QAction(self.label, tray_menu)
            tray_menu.addAction(action)

        action.triggered.connect(self.on_action_trigger)
        self._action_item = action

    def tray_start(self):
        return

    def tray_exit(self):
        return

label abstractmethod property

Service label showed in menu.

on_action_trigger() abstractmethod

What happens on actions click.

Source code in client/ayon_core/addon/interfaces.py
251
252
253
254
@abstractmethod
def on_action_trigger(self):
    """What happens on actions click."""
    pass

ITrayAddon

Bases: AYONInterface

Addon has special procedures when used in Tray tool.

IMPORTANT: The addon. still must be usable if is not used in tray even if would do nothing.

Source code in client/ayon_core/addon/interfaces.py
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
class ITrayAddon(AYONInterface):
    """Addon has special procedures when used in Tray tool.

    IMPORTANT:
    The addon. still must be usable if is not used in tray even if
    would do nothing.
    """

    tray_initialized = False
    _tray_manager = None
    _admin_submenu = None

    @abstractmethod
    def tray_init(self):
        """Initialization part of tray implementation.

        Triggered between `initialization` and `connect_with_addons`.

        This is where GUIs should be loaded or tray specific parts should be
        prepared.
        """

        pass

    @abstractmethod
    def tray_menu(self, tray_menu):
        """Add addon's action to tray menu."""

        pass

    @abstractmethod
    def tray_start(self):
        """Start procedure in tray tool."""

        pass

    @abstractmethod
    def tray_exit(self):
        """Cleanup method which is executed on tray shutdown.

        This is place where all threads should be shut.
        """

        pass

    def execute_in_main_thread(self, callback):
        """ Pushes callback to the queue or process 'callback' on a main thread

            Some callbacks need to be processed on main thread (menu actions
            must be added on main thread or they won't get triggered etc.)
        """

        if not self.tray_initialized:
            # TODO Called without initialized tray, still main thread needed
            try:
                callback()

            except Exception:
                self.log.warning(
                    "Failed to execute {} in main thread".format(callback),
                    exc_info=True)

            return
        self.manager.tray_manager.execute_in_main_thread(callback)

    def show_tray_message(self, title, message, icon=None, msecs=None):
        """Show tray message.

        Args:
            title (str): Title of message.
            message (str): Content of message.
            icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is
                Information icon, may differ by Qt version.
            msecs (int): Duration of message visibility in milliseconds.
                Default is 10000 msecs, may differ by Qt version.
        """

        if self._tray_manager:
            self._tray_manager.show_tray_message(title, message, icon, msecs)

    def add_doubleclick_callback(self, callback):
        if hasattr(self.manager, "add_doubleclick_callback"):
            self.manager.add_doubleclick_callback(self, callback)

    @staticmethod
    def admin_submenu(tray_menu):
        if ITrayAddon._admin_submenu is None:
            from qtpy import QtWidgets

            admin_submenu = QtWidgets.QMenu("Admin", tray_menu)
            admin_submenu.menuAction().setVisible(False)
            ITrayAddon._admin_submenu = admin_submenu
        return ITrayAddon._admin_submenu

    @staticmethod
    def add_action_to_admin_submenu(label, tray_menu):
        from qtpy import QtWidgets

        menu = ITrayAddon.admin_submenu(tray_menu)
        action = QtWidgets.QAction(label, menu)
        menu.addAction(action)
        if not menu.menuAction().isVisible():
            menu.menuAction().setVisible(True)
        return action

execute_in_main_thread(callback)

Pushes callback to the queue or process 'callback' on a main thread

Some callbacks need to be processed on main thread (menu actions must be added on main thread or they won't get triggered etc.)

Source code in client/ayon_core/addon/interfaces.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def execute_in_main_thread(self, callback):
    """ Pushes callback to the queue or process 'callback' on a main thread

        Some callbacks need to be processed on main thread (menu actions
        must be added on main thread or they won't get triggered etc.)
    """

    if not self.tray_initialized:
        # TODO Called without initialized tray, still main thread needed
        try:
            callback()

        except Exception:
            self.log.warning(
                "Failed to execute {} in main thread".format(callback),
                exc_info=True)

        return
    self.manager.tray_manager.execute_in_main_thread(callback)

show_tray_message(title, message, icon=None, msecs=None)

Show tray message.

Parameters:

Name Type Description Default
title str

Title of message.

required
message str

Content of message.

required
icon MessageIcon

Message's icon. Default is Information icon, may differ by Qt version.

None
msecs int

Duration of message visibility in milliseconds. Default is 10000 msecs, may differ by Qt version.

None
Source code in client/ayon_core/addon/interfaces.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def show_tray_message(self, title, message, icon=None, msecs=None):
    """Show tray message.

    Args:
        title (str): Title of message.
        message (str): Content of message.
        icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is
            Information icon, may differ by Qt version.
        msecs (int): Duration of message visibility in milliseconds.
            Default is 10000 msecs, may differ by Qt version.
    """

    if self._tray_manager:
        self._tray_manager.show_tray_message(title, message, icon, msecs)

tray_exit() abstractmethod

Cleanup method which is executed on tray shutdown.

This is place where all threads should be shut.

Source code in client/ayon_core/addon/interfaces.py
161
162
163
164
165
166
167
168
@abstractmethod
def tray_exit(self):
    """Cleanup method which is executed on tray shutdown.

    This is place where all threads should be shut.
    """

    pass

tray_init() abstractmethod

Initialization part of tray implementation.

Triggered between initialization and connect_with_addons.

This is where GUIs should be loaded or tray specific parts should be prepared.

Source code in client/ayon_core/addon/interfaces.py
137
138
139
140
141
142
143
144
145
146
147
@abstractmethod
def tray_init(self):
    """Initialization part of tray implementation.

    Triggered between `initialization` and `connect_with_addons`.

    This is where GUIs should be loaded or tray specific parts should be
    prepared.
    """

    pass

tray_menu(tray_menu) abstractmethod

Add addon's action to tray menu.

Source code in client/ayon_core/addon/interfaces.py
149
150
151
152
153
@abstractmethod
def tray_menu(self, tray_menu):
    """Add addon's action to tray menu."""

    pass

tray_start() abstractmethod

Start procedure in tray tool.

Source code in client/ayon_core/addon/interfaces.py
155
156
157
158
159
@abstractmethod
def tray_start(self):
    """Start procedure in tray tool."""

    pass

ITrayService

Bases: ITrayAddon

Source code in client/ayon_core/addon/interfaces.py
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
class ITrayService(ITrayAddon):
    # Module's property
    menu_action = None

    # Class properties
    _services_submenu = None
    _icon_failed = None
    _icon_running = None
    _icon_idle = None

    @property
    @abstractmethod
    def label(self):
        """Service label showed in menu."""
        pass

    # TODO be able to get any sort of information to show/print
    # @abstractmethod
    # def get_service_info(self):
    #     pass

    @staticmethod
    def services_submenu(tray_menu):
        if ITrayService._services_submenu is None:
            from qtpy import QtWidgets

            services_submenu = QtWidgets.QMenu("Services", tray_menu)
            services_submenu.menuAction().setVisible(False)
            ITrayService._services_submenu = services_submenu
        return ITrayService._services_submenu

    @staticmethod
    def add_service_action(action):
        ITrayService._services_submenu.addAction(action)
        if not ITrayService._services_submenu.menuAction().isVisible():
            ITrayService._services_submenu.menuAction().setVisible(True)

    @staticmethod
    def _load_service_icons():
        from qtpy import QtGui

        ITrayService._failed_icon = QtGui.QIcon(
            resources.get_resource("icons", "circle_red.png")
        )
        ITrayService._icon_running = QtGui.QIcon(
            resources.get_resource("icons", "circle_green.png")
        )
        ITrayService._icon_idle = QtGui.QIcon(
            resources.get_resource("icons", "circle_orange.png")
        )

    @staticmethod
    def get_icon_running():
        if ITrayService._icon_running is None:
            ITrayService._load_service_icons()
        return ITrayService._icon_running

    @staticmethod
    def get_icon_idle():
        if ITrayService._icon_idle is None:
            ITrayService._load_service_icons()
        return ITrayService._icon_idle

    @staticmethod
    def get_icon_failed():
        if ITrayService._failed_icon is None:
            ITrayService._load_service_icons()
        return ITrayService._failed_icon

    def tray_menu(self, tray_menu):
        from qtpy import QtWidgets

        action = QtWidgets.QAction(
            self.label,
            self.services_submenu(tray_menu)
        )
        self.menu_action = action

        self.add_service_action(action)

        self.set_service_running_icon()

    def set_service_running_icon(self):
        """Change icon of an QAction to green circle."""

        if self.menu_action:
            self.menu_action.setIcon(self.get_icon_running())

    def set_service_failed_icon(self):
        """Change icon of an QAction to red circle."""

        if self.menu_action:
            self.menu_action.setIcon(self.get_icon_failed())

    def set_service_idle_icon(self):
        """Change icon of an QAction to orange circle."""

        if self.menu_action:
            self.menu_action.setIcon(self.get_icon_idle())

label abstractmethod property

Service label showed in menu.

set_service_failed_icon()

Change icon of an QAction to red circle.

Source code in client/ayon_core/addon/interfaces.py
363
364
365
366
367
def set_service_failed_icon(self):
    """Change icon of an QAction to red circle."""

    if self.menu_action:
        self.menu_action.setIcon(self.get_icon_failed())

set_service_idle_icon()

Change icon of an QAction to orange circle.

Source code in client/ayon_core/addon/interfaces.py
369
370
371
372
373
def set_service_idle_icon(self):
    """Change icon of an QAction to orange circle."""

    if self.menu_action:
        self.menu_action.setIcon(self.get_icon_idle())

set_service_running_icon()

Change icon of an QAction to green circle.

Source code in client/ayon_core/addon/interfaces.py
357
358
359
360
361
def set_service_running_icon(self):
    """Change icon of an QAction to green circle."""

    if self.menu_action:
        self.menu_action.setIcon(self.get_icon_running())

ProcessContext

Hold context of process that is going to be started.

Right now the context is simple, having information about addon that wants to trigger preparation and possibly project name for which it should happen.

Preparation for process can be required for ayon-core or any other addon. It can be, change of environment variables, or request login to a project management.

At the moment of creation is 'ProcessContext' only data holder, but that might change in future if there will be need.

Parameters:

Name Type Description Default
addon_name str

Addon name which triggered process.

required
addon_version str

Addon version which triggered process.

required
project_name Optional[str]

Project name. Can be filled in case process is triggered for specific project. Some addons can have different behavior based on project. Value is NOT autofilled.

None
headless Optional[bool]

Is process running in headless mode. Value is filled with value based on state set in AYON launcher.

None
Source code in client/ayon_core/addon/base.py
 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
class ProcessContext:
    """Hold context of process that is going to be started.

    Right now the context is simple, having information about addon that wants
        to trigger preparation and possibly project name for which it should
        happen.

    Preparation for process can be required for ayon-core or any other addon.
        It can be, change of environment variables, or request login to
        a project management.

    At the moment of creation is 'ProcessContext' only data holder, but that
        might change in future if there will be need.

    Args:
        addon_name (str): Addon name which triggered process.
        addon_version (str): Addon version which triggered process.
        project_name (Optional[str]): Project name. Can be filled in case
            process is triggered for specific project. Some addons can have
            different behavior based on project. Value is NOT autofilled.
        headless (Optional[bool]): Is process running in headless mode. Value
            is filled with value based on state set in AYON launcher.

    """
    def __init__(
        self,
        addon_name: str,
        addon_version: str,
        project_name: Optional[str] = None,
        headless: Optional[bool] = None,
        **kwargs,
    ):
        if headless is None:
            headless = is_headless_mode_enabled()
        self.addon_name: str = addon_name
        self.addon_version: str = addon_version
        self.project_name: Optional[str] = project_name
        self.headless: bool = headless

        if kwargs:
            unknown_keys = ", ".join([f'"{key}"' for key in kwargs.keys()])
            print(f"Unknown keys in ProcessContext: {unknown_keys}")

ProcessPreparationError

Bases: Exception

Exception that can be used when process preparation failed.

The message is shown to user (either as UI dialog or printed). If different error is raised a "generic" error message is shown to user with option to copy error message to clipboard.

Source code in client/ayon_core/addon/base.py
69
70
71
72
73
74
75
76
77
class ProcessPreparationError(Exception):
    """Exception that can be used when process preparation failed.

    The message is shown to user (either as UI dialog or printed). If
        different error is raised a "generic" error message is shown to user
        with option to copy error message to clipboard.

    """
    pass

ensure_addons_are_process_context_ready(process_context, addons_manager=None, exit_on_failure=True)

Ensure all enabled addons are ready to be used in the given context.

Call this method only in AYON launcher process and as first thing to avoid possible clashes with preparation. For example 'QApplication' should not be created.

Todos

Run all preparations and allow to "ignore" failed preparations. Right now single addon can block using certain actions.

Parameters:

Name Type Description Default
process_context ProcessContext

The context in which the addons should be prepared.

required
addons_manager Optional[AddonsManager]

The addons manager to use. If not provided, a new one will be created.

None
exit_on_failure bool

If True, the process will exit if an error occurs. Defaults to True.

True

Returns:

Name Type Description
bool bool

True if all addons are ready, False otherwise.

Source code in client/ayon_core/addon/utils.py
 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
def ensure_addons_are_process_context_ready(
    process_context: ProcessContext,
    addons_manager: Optional[AddonsManager] = None,
    exit_on_failure: bool = True,
) -> bool:
    """Ensure all enabled addons are ready to be used in the given context.

    Call this method only in AYON launcher process and as first thing
        to avoid possible clashes with preparation. For example 'QApplication'
        should not be created.

    Todos:
        Run all preparations and allow to "ignore" failed preparations.
            Right now single addon can block using certain actions.

    Args:
        process_context (ProcessContext): The context in which the
            addons should be prepared.
        addons_manager (Optional[AddonsManager]): The addons
            manager to use. If not provided, a new one will be created.
        exit_on_failure (bool, optional): If True, the process will exit
            if an error occurs. Defaults to True.

    Returns:
        bool: True if all addons are ready, False otherwise.

    """
    if addons_manager is None:
        addons_manager = AddonsManager()

    message = None
    failed = False
    use_detail = False
    # Wrap the output in StringIO to capture it for details on fail
    # - but in case stdout was invalid on start of process also store
    #   the tracebacks
    tracebacks = []
    output = StringIO()
    with contextlib.redirect_stdout(output):
        with contextlib.redirect_stderr(output):
            for addon in addons_manager.get_enabled_addons():
                addon_failed = True
                try:
                    addon.ensure_is_process_ready(process_context)
                    addon_failed = False
                except ProcessPreparationError as exc:
                    message = str(exc)
                    print(f"Addon preparation failed: '{addon.name}'")
                    print(message)

                except BaseException:
                    use_detail = True
                    message = "An unexpected error occurred."
                    formatted_traceback = "".join(traceback.format_exception(
                        *sys.exc_info()
                    ))
                    tracebacks.append(formatted_traceback)
                    print(f"Addon preparation failed: '{addon.name}'")
                    print(message)
                    # Print the traceback so it is in the stdout
                    print(formatted_traceback)

                if addon_failed:
                    failed = True
                    break

    output_str = output.getvalue()
    # Print stdout/stderr to console as it was redirected
    print(output_str)
    if not failed:
        if not process_context.headless:
            _start_tray()
        return True

    detail = None
    if use_detail:
        # In case stdout was not captured, use the tracebacks as detail
        if not output_str:
            output_str = "\n".join(tracebacks)
        detail = output_str

    _handle_error(process_context, message, detail)
    if exit_on_failure:
        sys.exit(1)
    return False

ensure_addons_are_process_ready(addon_name, addon_version, project_name=None, headless=None, *, addons_manager=None, exit_on_failure=True, **kwargs)

Ensure all enabled addons are ready to be used in the given context.

Call this method only in AYON launcher process and as first thing to avoid possible clashes with preparation. For example 'QApplication' should not be created.

Parameters:

Name Type Description Default
addon_name str

Addon name which triggered process.

required
addon_version str

Addon version which triggered process.

required
project_name Optional[str]

Project name. Can be filled in case process is triggered for specific project. Some addons can have different behavior based on project. Value is NOT autofilled.

None
headless Optional[bool]

Is process running in headless mode. Value is filled with value based on state set in AYON launcher.

None
addons_manager Optional[AddonsManager]

The addons manager to use. If not provided, a new one will be created.

None
exit_on_failure bool

If True, the process will exit if an error occurs. Defaults to True.

True
kwargs

The keyword arguments to pass to the ProcessContext.

{}

Returns:

Name Type Description
bool bool

True if all addons are ready, False otherwise.

Source code in client/ayon_core/addon/utils.py
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
def ensure_addons_are_process_ready(
    addon_name: str,
    addon_version: str,
    project_name: Optional[str] = None,
    headless: Optional[bool] = None,
    *,
    addons_manager: Optional[AddonsManager] = None,
    exit_on_failure: bool = True,
    **kwargs,
) -> bool:
    """Ensure all enabled addons are ready to be used in the given context.

    Call this method only in AYON launcher process and as first thing
        to avoid possible clashes with preparation. For example 'QApplication'
        should not be created.

    Args:
        addon_name (str): Addon name which triggered process.
        addon_version (str): Addon version which triggered process.
        project_name (Optional[str]): Project name. Can be filled in case
            process is triggered for specific project. Some addons can have
            different behavior based on project. Value is NOT autofilled.
        headless (Optional[bool]): Is process running in headless mode. Value
            is filled with value based on state set in AYON launcher.
        addons_manager (Optional[AddonsManager]): The addons
            manager to use. If not provided, a new one will be created.
        exit_on_failure (bool, optional): If True, the process will exit
            if an error occurs. Defaults to True.
        kwargs: The keyword arguments to pass to the ProcessContext.

    Returns:
        bool: True if all addons are ready, False otherwise.

    """
    context: ProcessContext = ProcessContext(
        addon_name,
        addon_version,
        project_name,
        headless,
        **kwargs
    )
    return ensure_addons_are_process_context_ready(
        context, addons_manager, exit_on_failure
    )

load_addons(force=False)

Load AYON addons as python modules.

Modules does not load only classes (like in Interfaces) because there must be ability to use inner code of addon and be able to import it from one defined place.

With this it is possible to import addon's content from predefined module.

Parameters:

Name Type Description Default
force bool

Force to load addons even if are already loaded. This won't update already loaded and used (cached) modules.

False
Source code in client/ayon_core/addon/base.py
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
def load_addons(force=False):
    """Load AYON addons as python modules.

    Modules does not load only classes (like in Interfaces) because there must
    be ability to use inner code of addon and be able to import it from one
    defined place.

    With this it is possible to import addon's content from predefined module.

    Args:
        force (bool): Force to load addons even if are already loaded.
            This won't update already loaded and used (cached) modules.
    """

    if _LoadCache.addons_loaded and not force:
        return

    if not _LoadCache.addons_lock.locked():
        with _LoadCache.addons_lock:
            _load_addons()
            _LoadCache.addons_loaded = True
    else:
        # If lock is locked wait until is finished
        while _LoadCache.addons_lock.locked():
            time.sleep(0.1)