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
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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.__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
44
45
46
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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.

        """
        registered_classes = self._registered_plugins.get(superclass) or []
        registered_paths = self._registered_plugin_paths.get(superclass) or []
        result = discover_plugins(
            superclass,
            paths=registered_paths,
            classes=registered_classes,
            ignored_classes=ignore_classes,
            allow_duplicates=allow_duplicates,
        )

        # 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
272
273
274
275
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
277
278
279
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
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
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.

    """
    registered_classes = self._registered_plugins.get(superclass) or []
    registered_paths = self._registered_plugin_paths.get(superclass) or []
    result = discover_plugins(
        superclass,
        paths=registered_paths,
        classes=registered_classes,
        ignored_classes=ignore_classes,
        allow_duplicates=allow_duplicates,
    )

    # 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
181
182
183
184
185
186
187
188
189
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
233
234
235
236
237
238
239
240
241
242
243
244
245
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
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
264
265
266
267
268
269
270
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
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
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
    )

discover_plugins(base_class, paths=None, classes=None, ignored_classes=None, allow_duplicates=True)

Find and return subclasses of superclass

Parameters:

Name Type Description Default
base_class type

Class which determines discovered subclasses.

required
paths Optional[list[str]]

List of paths to look for plug-ins.

None
classes Optional[list[str]]

List of classes to filter.

None
ignored_classes list[type]

List of classes that won't be added to the output plugins.

None
allow_duplicates bool

Validate class name duplications.

True

Returns:

Name Type Description
DiscoverResult

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
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
def discover_plugins(
    base_class: type,
    paths: Optional[list[str]] = None,
    classes: Optional[list[type]] = None,
    ignored_classes: Optional[list[type]] = None,
    allow_duplicates: bool = True,
):
    """Find and return subclasses of `superclass`

    Args:
        base_class (type): Class which determines discovered subclasses.
        paths (Optional[list[str]]): List of paths to look for plug-ins.
        classes (Optional[list[str]]): List of classes to filter.
        ignored_classes (list[type]): List of classes that won't be added to
            the output plugins.
        allow_duplicates (bool): Validate class name duplications.

    Returns:
        DiscoverResult: Object holding successfully
            discovered plugins, ignored plugins, plugins with missing
            abstract implementation and duplicated plugin.

    """
    ignored_classes = ignored_classes or []
    paths = paths or []
    classes = classes or []

    result = DiscoverResult(base_class)

    all_plugins = list(classes)

    for path in paths:
        modules, crashed = modules_from_path(path)
        for (filepath, exc_info) in crashed:
            result.crashed_file_paths[filepath] = exc_info

        for item in modules:
            filepath, module = item
            result.add_module(module)
            all_plugins.extend(classes_from_module(base_class, module))

    if base_class not in ignored_classes:
        ignored_classes.append(base_class)

    plugin_names = set()
    for cls in all_plugins:
        if cls in ignored_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)
    return result