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
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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: ModuleType) -> None:
        """Add dynamically loaded python module to keep it in memory."""
        self._modules.add(module)

    def ignore_plugins(self, plugins: list[type]) -> None:
        """Add plugins to be ignored from the result."""
        for plugin in plugins:
            if plugin in self.ignored_plugins:
                continue
            if plugin in self.plugins:
                self.plugins.remove(plugin)
            self.ignored_plugins.add(plugin)

    def remove_duplicates(self) -> None:
        """Remove duplicated plugins from the result."""
        plugin_names: set[str] = set()
        for plugin in tuple(self.plugins):
            class_name = plugin.__name__
            if class_name in plugin_names:
                self.plugins.remove(plugin)
                self.duplicated_plugins.append(plugin)
                continue
            plugin_names.add(class_name)

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

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

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

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

        if self.crashed_file_paths or full_report:
            lines.append(
                f"*** Failed to load {len(self.crashed_file_paths)} files"
            )
            for path, exc_info_args in self.crashed_file_paths.items():
                lines.append(f"- {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: bool = True, exc_info: bool = True,
    ) -> None:
        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
72
73
74
def add_module(self, module: ModuleType) -> None:
    """Add dynamically loaded python module to keep it in memory."""
    self._modules.add(module)

ignore_plugins(plugins)

Add plugins to be ignored from the result.

Source code in client/ayon_core/pipeline/plugin_discover.py
76
77
78
79
80
81
82
83
def ignore_plugins(self, plugins: list[type]) -> None:
    """Add plugins to be ignored from the result."""
    for plugin in plugins:
        if plugin in self.ignored_plugins:
            continue
        if plugin in self.plugins:
            self.plugins.remove(plugin)
        self.ignored_plugins.add(plugin)

remove_duplicates()

Remove duplicated plugins from the result.

Source code in client/ayon_core/pipeline/plugin_discover.py
85
86
87
88
89
90
91
92
93
94
def remove_duplicates(self) -> None:
    """Remove duplicated plugins from the result."""
    plugin_names: set[str] = set()
    for plugin in tuple(self.plugins):
        class_name = plugin.__name__
        if class_name in plugin_names:
            self.plugins.remove(plugin)
            self.duplicated_plugins.append(plugin)
            continue
        plugin_names.add(class_name)

DiscoveryDefinition dataclass

Superclass discovery definition.

Attributes:

Name Type Description
superclass type

Superclass to discover subclasses in paths.

paths list[str]

List of paths to look for plug-ins that do inherit from the superclass.

classes list[type]

List of classes that are not discovered from paths but are explicitly defined. Should inherit from the superclass.

Source code in client/ayon_core/pipeline/plugin_discover.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@dataclass
class DiscoveryDefinition:
    """Superclass discovery definition.

    Attributes:
        superclass (type): Superclass to discover subclasses in paths.
        paths (list[str]): List of paths to look for plug-ins that do inherit
            from the superclass.
        classes (list[type]): List of classes that are not discovered from
            paths but are explicitly defined. Should inherit from
                the superclass.

    """
    superclass: type
    paths: list[str] = field(default_factory=list)
    classes: list[type] = field(default_factory=list)

PluginDiscoverContext

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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
class PluginDiscoverContext:
    """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 discover_with_report(
        self, superclasses: Iterable[type]
    ) -> dict[type, DiscoverResult]:
        """Find and return subclasses of superclasses.

        Args:
            superclasses (Iterable[type]): Classes which determines discovered
                subclasses.

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

        """
        defs = [
            DiscoveryDefinition(
                superclass=superclass,
                paths=self._registered_plugin_paths.get(superclass) or [],
                classes=self._registered_plugins.get(superclass) or [],
            )
            for superclass in superclasses
        ]
        result = discover_plugins_with_defs(defs)

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

    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
410
411
412
413
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
415
416
417
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
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
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

discover_with_report(superclasses)

Find and return subclasses of superclasses.

Parameters:

Name Type Description Default
superclasses Iterable[type]

Classes which determines discovered subclasses.

required

Returns:

Type Description
dict[type, DiscoverResult]

dict[type, 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
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
def discover_with_report(
    self, superclasses: Iterable[type]
) -> dict[type, DiscoverResult]:
    """Find and return subclasses of superclasses.

    Args:
        superclasses (Iterable[type]): Classes which determines discovered
            subclasses.

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

    """
    defs = [
        DiscoveryDefinition(
            superclass=superclass,
            paths=self._registered_plugin_paths.get(superclass) or [],
            classes=self._registered_plugins.get(superclass) or [],
        )
        for superclass in superclasses
    ]
    result = discover_plugins_with_defs(defs)

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

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
285
286
287
288
289
290
291
292
293
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
371
372
373
374
375
376
377
378
379
380
381
382
383
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
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
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
402
403
404
405
406
407
408
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
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
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 list[str] | None

List of paths to look for plug-ins.

None
classes list[str] | None

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

    Args:
        base_class (type): Class which determines discovered subclasses.
        paths (list[str] | None): List of paths to look for plug-ins.
        classes (list[str] | None): 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.

    """
    result = discover_plugins_with_defs([
        DiscoveryDefinition(
            base_class,
            paths=paths or [],
            classes=classes or [],
        ),
    ])[base_class]
    result.ignore_plugins(ignored_classes or [])
    if not allow_duplicates:
        result.remove_duplicates()
    return result

discover_plugins_with_defs(superclass_defs)

Find and return subclasses.

Parameters:

Name Type Description Default
superclass_defs list[DiscoveryDefinition]

List of superclasses with their paths and classes to discover.

required

Returns:

Type Description
dict[type, DiscoverResult]

dict[type, DiscoverResult]: Discover result by superclass.

Source code in client/ayon_core/pipeline/plugin_discover.py
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
def discover_plugins_with_defs(
    superclass_defs: list[DiscoveryDefinition],
) -> dict[type, DiscoverResult]:
    """Find and return subclasses.

    Args:
        superclass_defs (list[DiscoveryDefinition]): List of superclasses with
            their paths and classes to discover.

    Returns:
        dict[type, DiscoverResult]: Discover result by superclass.

    """
    normalized_paths: dict[str, Path] = {}
    paths_by_superclass: dict[type, set[str]] = {}
    results: dict[type, DiscoverResult] = {}
    classes_by_superclass: dict[type, list[type]] = {}
    for superclass_def in superclass_defs:
        superclass_paths = set()
        for path in superclass_def.paths:
            path = Path(path)
            if not path.is_absolute():
                log.warning(
                    "Relative paths are not allowed for"
                    f" security reasons '{path}'."
                )
                continue

            if not path.exists():
                continue

            unique_path = path.as_posix()
            if IS_WINDOWS:
                unique_path = unique_path.lower()
            normalized_paths[unique_path] = path
            superclass_paths.add(unique_path)

        superclass = superclass_def.superclass
        classes_by_superclass[superclass] = list(superclass_def.classes)
        paths_by_superclass[superclass] = superclass_paths
        results[superclass] = DiscoverResult(superclass)

    for unique_path, path in normalized_paths.items():
        import_result: ModulesResult = modules_from_path(path)
        for item in import_result.crashed:
            for superclass, superclass_paths in paths_by_superclass.items():
                if unique_path in superclass_paths:
                    results[superclass].crashed_file_paths[item.filepath] = (
                        item.exc_info
                    )

        for item in import_result.modules:
            for superclass, superclass_paths in paths_by_superclass.items():
                if unique_path not in superclass_paths:
                    continue

                results[superclass].add_module(item.module)
                classes_by_superclass[superclass].extend(
                    classes_from_module(superclass, item.module)
                )

    for superclass, classes in classes_by_superclass.items():
        sc_result = results[superclass]
        for cls in classes:
            if cls is superclass:
                continue
            # Class has defined 'skip_discovery = True'
            skip_discovery = cls.__dict__.get("skip_discovery")
            if skip_discovery is True:
                continue
            if inspect.isabstract(cls):
                sc_result.abstract_plugins.append(cls)
                continue
            sc_result.plugins.append(cls)
    return results

discover_with_report(superclasses)

Find and return subclasses of superclasses.

Parameters:

Name Type Description Default
superclasses Iterable[type]

Class which determines discovered subclasses.

required

Returns:

Type Description
dict[type, DiscoverResult]

dict[type, DiscoverResult]: Report by super class.

Source code in client/ayon_core/pipeline/plugin_discover.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
def discover_with_report(
    superclasses: Iterable[type],
) -> dict[type, DiscoverResult]:
    """Find and return subclasses of superclasses.

    Args:
        superclasses (Iterable[type]): Class which
            determines discovered subclasses.

    Returns:
        dict[type, DiscoverResult]: Report by super class.

    """
    context = _GlobalDiscover.get_context()
    return context.discover_with_report(superclasses)