Skip to content

plugin_discover

DiscoverResult

Result of Plug-ins discovery of a single superclass type.

Stores discovered, duplicated, ignored and abstract plugins and file paths which crashed on execution of file.

Source code in client/ayon_core/pipeline/plugin_discover.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
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
class DiscoverResult:
    """Result of Plug-ins discovery of a single superclass type.

    Stores discovered, duplicated, ignored and abstract plugins and file paths
    which crashed on execution of file.
    """

    def __init__(self, superclass):
        self.superclass = superclass
        self.plugins = []
        self.crashed_file_paths = {}
        self.duplicated_plugins = []
        self.abstract_plugins = []
        self.ignored_plugins = set()
        # Store loaded modules to keep them in memory
        self._modules = set()

    def __iter__(self):
        for plugin in self.plugins:
            yield plugin

    def __getitem__(self, item):
        return self.plugins[item]

    def __setitem__(self, item, value):
        self.plugins[item] = value

    def add_module(self, module):
        """Add dynamically loaded python module to keep it in memory."""
        self._modules.add(module)

    def get_report(self, only_errors=True, exc_info=True, full_report=False):
        lines = []
        if not only_errors:
            # Successfully discovered plugins
            if self.plugins or full_report:
                lines.append(
                    "*** Discovered {} plugins".format(len(self.plugins))
                )
                for cls in self.plugins:
                    lines.append("- {}".format(cls.__class__.__name__))

            # Plugin that were defined to be ignored
            if self.ignored_plugins or full_report:
                lines.append("*** Ignored plugins {}".format(len(
                    self.ignored_plugins
                )))
                for cls in self.ignored_plugins:
                    lines.append("- {}".format(cls.__name__))

        # Abstract classes
        if self.abstract_plugins or full_report:
            lines.append("*** Discovered {} abstract plugins".format(len(
                self.abstract_plugins
            )))
            for cls in self.abstract_plugins:
                lines.append("- {}".format(cls.__name__))

        # Abstract classes
        if self.duplicated_plugins or full_report:
            lines.append("*** There were {} duplicated plugins".format(len(
                self.duplicated_plugins
            )))
            for cls in self.duplicated_plugins:
                lines.append("- {}".format(cls.__name__))

        if self.crashed_file_paths or full_report:
            lines.append("*** Failed to load {} files".format(len(
                self.crashed_file_paths
            )))
            for path, exc_info_args in self.crashed_file_paths.items():
                lines.append("- {}".format(path))
                if exc_info:
                    lines.append(10 * "*")
                    lines.extend(traceback.format_exception(*exc_info_args))
                    lines.append(10 * "*")

        return "\n".join(lines)

    def log_report(self, only_errors=True, exc_info=True):
        report = self.get_report(only_errors, exc_info)
        if report:
            log.info(report)

add_module(module)

Add dynamically loaded python module to keep it in memory.

Source code in client/ayon_core/pipeline/plugin_discover.py
41
42
43
def add_module(self, module):
    """Add dynamically loaded python module to keep it in memory."""
    self._modules.add(module)

PluginDiscoverContext

Bases: object

Store and discover registered types nad registered paths to types.

Keeps in memory all registered types and their paths. Paths are dynamically loaded on discover so different discover calls won't return the same class objects even if were loaded from same file.

Source code in client/ayon_core/pipeline/plugin_discover.py
 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
class PluginDiscoverContext(object):
    """Store and discover registered types nad registered paths to types.

    Keeps in memory all registered types and their paths. Paths are dynamically
    loaded on discover so different discover calls won't return the same
    class objects even if were loaded from same file.
    """

    def __init__(self):
        self._registered_plugins = {}
        self._registered_plugin_paths = {}
        self._last_discovered_plugins = {}
        # Store the last result to memory
        self._last_discovered_results = {}

    def get_last_discovered_plugins(self, superclass):
        """Access last discovered plugin by a subperclass.

        Returns:
            None: When superclass was not discovered yet.
            list: Lastly discovered plugins of the superclass.
        """

        return self._last_discovered_plugins.get(superclass)

    def discover(
        self,
        superclass,
        allow_duplicates=True,
        ignore_classes=None,
        return_report=False
    ):
        """Find and return subclasses of `superclass`

        Args:
            superclass (type): Class which determines discovered subclasses.
            allow_duplicates (bool): Validate class name duplications.
            ignore_classes (list): List of classes that will be ignored
                and not added to result.
            return_report (bool): Output will be full report if set to 'True'.

        Returns:
            Union[DiscoverResult, list[Any]]: Object holding successfully
                discovered plugins, ignored plugins, plugins with missing
                abstract implementation and duplicated plugin.
        """

        if not ignore_classes:
            ignore_classes = []

        result = DiscoverResult(superclass)
        plugin_names = set()
        registered_classes = self._registered_plugins.get(superclass) or []
        registered_paths = self._registered_plugin_paths.get(superclass) or []
        for cls in registered_classes:
            if cls is superclass or cls in ignore_classes:
                result.ignored_plugins.add(cls)
                continue

            if inspect.isabstract(cls):
                result.abstract_plugins.append(cls)
                continue

            class_name = cls.__name__
            if class_name in plugin_names:
                result.duplicated_plugins.append(cls)
                continue
            plugin_names.add(class_name)
            result.plugins.append(cls)

        # Include plug-ins from registered paths
        for path in registered_paths:
            modules, crashed = modules_from_path(path)
            for item in crashed:
                filepath, exc_info = item
                result.crashed_file_paths[filepath] = exc_info

            for item in modules:
                filepath, module = item
                result.add_module(module)
                for cls in classes_from_module(superclass, module):
                    if cls is superclass or cls in ignore_classes:
                        result.ignored_plugins.add(cls)
                        continue

                    if inspect.isabstract(cls):
                        result.abstract_plugins.append(cls)
                        continue

                    if not allow_duplicates:
                        class_name = cls.__name__
                        if class_name in plugin_names:
                            result.duplicated_plugins.append(cls)
                            continue
                        plugin_names.add(class_name)

                    result.plugins.append(cls)

        # Store in memory last result to keep in memory loaded modules
        self._last_discovered_results[superclass] = result
        self._last_discovered_plugins[superclass] = list(
            result.plugins
        )
        result.log_report()
        if return_report:
            return result
        return result.plugins

    def register_plugin(self, superclass, cls):
        """Register a directory containing plug-ins of type `superclass`

        Arguments:
            superclass (type): Superclass of plug-in
            cls (object): Subclass of `superclass`
        """

        if superclass not in self._registered_plugins:
            self._registered_plugins[superclass] = list()

        if cls not in self._registered_plugins[superclass]:
            self._registered_plugins[superclass].append(cls)

    def register_plugin_path(self, superclass, path):
        """Register a directory of one or more plug-ins

        Arguments:
            superclass (type): Superclass of plug-ins to look for during
                discovery
            path (str): Absolute path to directory in which to discover
                plug-ins
        """

        if superclass not in self._registered_plugin_paths:
            self._registered_plugin_paths[superclass] = list()

        path = os.path.normpath(path)
        if path not in self._registered_plugin_paths[superclass]:
            self._registered_plugin_paths[superclass].append(path)

    def registered_plugin_paths(self):
        """Return all currently registered plug-in paths"""
        # Return shallow copy so we the original data can't be changed
        return {
            superclass: paths[:]
            for superclass, paths in self._registered_plugin_paths.items()
        }

    def deregister_plugin(self, superclass, plugin):
        """Opposite of `register_plugin()`"""
        if superclass in self._registered_plugins:
            self._registered_plugins[superclass].remove(plugin)

    def deregister_plugin_path(self, superclass, path):
        """Opposite of `register_plugin_path()`"""
        self._registered_plugin_paths[superclass].remove(path)

deregister_plugin(superclass, plugin)

Opposite of register_plugin()

Source code in client/ayon_core/pipeline/plugin_discover.py
246
247
248
249
def deregister_plugin(self, superclass, plugin):
    """Opposite of `register_plugin()`"""
    if superclass in self._registered_plugins:
        self._registered_plugins[superclass].remove(plugin)

deregister_plugin_path(superclass, path)

Opposite of register_plugin_path()

Source code in client/ayon_core/pipeline/plugin_discover.py
251
252
253
def deregister_plugin_path(self, superclass, path):
    """Opposite of `register_plugin_path()`"""
    self._registered_plugin_paths[superclass].remove(path)

discover(superclass, allow_duplicates=True, ignore_classes=None, return_report=False)

Find and return subclasses of superclass

Parameters:

Name Type Description Default
superclass type

Class which determines discovered subclasses.

required
allow_duplicates bool

Validate class name duplications.

True
ignore_classes list

List of classes that will be ignored and not added to result.

None
return_report bool

Output will be full report if set to 'True'.

False

Returns:

Type Description

Union[DiscoverResult, list[Any]]: Object holding successfully discovered plugins, ignored plugins, plugins with missing abstract implementation and duplicated plugin.

Source code in client/ayon_core/pipeline/plugin_discover.py
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
def discover(
    self,
    superclass,
    allow_duplicates=True,
    ignore_classes=None,
    return_report=False
):
    """Find and return subclasses of `superclass`

    Args:
        superclass (type): Class which determines discovered subclasses.
        allow_duplicates (bool): Validate class name duplications.
        ignore_classes (list): List of classes that will be ignored
            and not added to result.
        return_report (bool): Output will be full report if set to 'True'.

    Returns:
        Union[DiscoverResult, list[Any]]: Object holding successfully
            discovered plugins, ignored plugins, plugins with missing
            abstract implementation and duplicated plugin.
    """

    if not ignore_classes:
        ignore_classes = []

    result = DiscoverResult(superclass)
    plugin_names = set()
    registered_classes = self._registered_plugins.get(superclass) or []
    registered_paths = self._registered_plugin_paths.get(superclass) or []
    for cls in registered_classes:
        if cls is superclass or cls in ignore_classes:
            result.ignored_plugins.add(cls)
            continue

        if inspect.isabstract(cls):
            result.abstract_plugins.append(cls)
            continue

        class_name = cls.__name__
        if class_name in plugin_names:
            result.duplicated_plugins.append(cls)
            continue
        plugin_names.add(class_name)
        result.plugins.append(cls)

    # Include plug-ins from registered paths
    for path in registered_paths:
        modules, crashed = modules_from_path(path)
        for item in crashed:
            filepath, exc_info = item
            result.crashed_file_paths[filepath] = exc_info

        for item in modules:
            filepath, module = item
            result.add_module(module)
            for cls in classes_from_module(superclass, module):
                if cls is superclass or cls in ignore_classes:
                    result.ignored_plugins.add(cls)
                    continue

                if inspect.isabstract(cls):
                    result.abstract_plugins.append(cls)
                    continue

                if not allow_duplicates:
                    class_name = cls.__name__
                    if class_name in plugin_names:
                        result.duplicated_plugins.append(cls)
                        continue
                    plugin_names.add(class_name)

                result.plugins.append(cls)

    # Store in memory last result to keep in memory loaded modules
    self._last_discovered_results[superclass] = result
    self._last_discovered_plugins[superclass] = list(
        result.plugins
    )
    result.log_report()
    if return_report:
        return result
    return result.plugins

get_last_discovered_plugins(superclass)

Access last discovered plugin by a subperclass.

Returns:

Name Type Description
None

When superclass was not discovered yet.

list

Lastly discovered plugins of the superclass.

Source code in client/ayon_core/pipeline/plugin_discover.py
114
115
116
117
118
119
120
121
122
def get_last_discovered_plugins(self, superclass):
    """Access last discovered plugin by a subperclass.

    Returns:
        None: When superclass was not discovered yet.
        list: Lastly discovered plugins of the superclass.
    """

    return self._last_discovered_plugins.get(superclass)

register_plugin(superclass, cls)

Register a directory containing plug-ins of type superclass

Parameters:

Name Type Description Default
superclass type

Superclass of plug-in

required
cls object

Subclass of superclass

required
Source code in client/ayon_core/pipeline/plugin_discover.py
207
208
209
210
211
212
213
214
215
216
217
218
219
def register_plugin(self, superclass, cls):
    """Register a directory containing plug-ins of type `superclass`

    Arguments:
        superclass (type): Superclass of plug-in
        cls (object): Subclass of `superclass`
    """

    if superclass not in self._registered_plugins:
        self._registered_plugins[superclass] = list()

    if cls not in self._registered_plugins[superclass]:
        self._registered_plugins[superclass].append(cls)

register_plugin_path(superclass, path)

Register a directory of one or more plug-ins

Parameters:

Name Type Description Default
superclass type

Superclass of plug-ins to look for during discovery

required
path str

Absolute path to directory in which to discover plug-ins

required
Source code in client/ayon_core/pipeline/plugin_discover.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def register_plugin_path(self, superclass, path):
    """Register a directory of one or more plug-ins

    Arguments:
        superclass (type): Superclass of plug-ins to look for during
            discovery
        path (str): Absolute path to directory in which to discover
            plug-ins
    """

    if superclass not in self._registered_plugin_paths:
        self._registered_plugin_paths[superclass] = list()

    path = os.path.normpath(path)
    if path not in self._registered_plugin_paths[superclass]:
        self._registered_plugin_paths[superclass].append(path)

registered_plugin_paths()

Return all currently registered plug-in paths

Source code in client/ayon_core/pipeline/plugin_discover.py
238
239
240
241
242
243
244
def registered_plugin_paths(self):
    """Return all currently registered plug-in paths"""
    # Return shallow copy so we the original data can't be changed
    return {
        superclass: paths[:]
        for superclass, paths in self._registered_plugin_paths.items()
    }

discover(superclass, allow_duplicates=True, ignore_classes=None, return_report=False)

Find and return subclasses of superclass

Parameters:

Name Type Description Default
superclass type

Class which determines discovered subclasses.

required
allow_duplicates bool

Validate class name duplications.

True
ignore_classes list

List of classes that will be ignored and not added to result.

None
return_report bool

Output will be full report if set to 'True'.

False

Returns:

Type Description

Union[DiscoverResult, list[Any]]: Object holding successfully discovered plugins, ignored plugins, plugins with missing abstract implementation and duplicated plugin.

Source code in client/ayon_core/pipeline/plugin_discover.py
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
def discover(
    superclass,
    allow_duplicates=True,
    ignore_classes=None,
    return_report=False
):
    """Find and return subclasses of `superclass`

    Args:
        superclass (type): Class which determines discovered subclasses.
        allow_duplicates (bool): Validate class name duplications.
        ignore_classes (list): List of classes that will be ignored
            and not added to result.
        return_report (bool): Output will be full report if set to 'True'.

    Returns:
        Union[DiscoverResult, list[Any]]: Object holding successfully
            discovered plugins, ignored plugins, plugins with missing
            abstract implementation and duplicated plugin.
    """

    context = _GlobalDiscover.get_context()
    return context.discover(
        superclass,
        allow_duplicates,
        ignore_classes,
        return_report
    )