pluginify 1.0.0.post11.dev0__py3-none-any.whl → 1.0.0.post41.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ import logging
17
17
  from importlib import metadata, resources, util, import_module
18
18
  from inspect import signature
19
19
  import json
20
- from os import getpid, makedirs, remove, replace
20
+ from os import makedirs, remove, replace
21
21
  from os.path import (
22
22
  basename,
23
23
  dirname,
@@ -29,7 +29,9 @@ from os.path import (
29
29
  )
30
30
  from pathlib import Path
31
31
  import re
32
+ import tempfile
32
33
  import warnings
34
+ from typing import Any
33
35
 
34
36
  import yaml
35
37
 
@@ -39,7 +41,7 @@ from pluginify.errors import PluginRegistryError
39
41
  LOG = logging.getLogger(__name__)
40
42
 
41
43
 
42
- def format_docstring(docstring, use_regex=True):
44
+ def format_docstring(docstring: str, use_regex: bool = True) -> str:
43
45
  """Format the provided docstring placement in the plugin registry.
44
46
 
45
47
  Found when using the CLI and inspecting the registry, some docstrings are formatted
@@ -49,12 +51,12 @@ def format_docstring(docstring, use_regex=True):
49
51
 
50
52
  Parameters
51
53
  ----------
52
- docstring: str
53
- - The docstring which we are going to format.
54
- use_regex: bool, optional (default=False)
55
- - Whether or not we want to apply regex formatting to the docstring. Usually
56
- recommended as it will replace 'newline' chars but not purposeful
57
- '.newline' strings.
54
+ docstring : str
55
+ The docstring which we are going to format.
56
+ use_regex : bool, optional (default=False)
57
+ Whether or not we want to apply regex formatting to the docstring. Usually
58
+ recommended as it will replace 'newline' chars but not purposeful
59
+ '.newline' strings.
58
60
  """
59
61
  if docstring:
60
62
  if use_regex:
@@ -70,23 +72,19 @@ def format_docstring(docstring, use_regex=True):
70
72
  return docstring
71
73
 
72
74
 
73
- def remove_registries(namespace, plugin_packages):
75
+ def remove_registries(namespace: str, plugin_packages: list) -> None:
74
76
  """Remove all plugin registries if a PluginRegistryError is raised.
75
77
 
76
78
  Parameters
77
79
  ----------
78
- namespace: str
80
+ namespace : str
79
81
  Namespace that your plugin packages fall under. The argument parser defaults
80
82
  this value to 'pluginify.plugin_packages', but a user can create separate
81
83
  namespaces if developing interfaces outside of pluginify.
82
- plugin_packages: list[EntryPoints]
84
+ plugin_packages : list[EntryPoints]
83
85
  A list of EntryPoints pointing to each installed package --> ie.
84
86
  [EntryPoint(name='pluginify', value='pluginify',
85
87
  group='pluginify.plugin_packages'), ...]
86
-
87
- Returns
88
- -------
89
- None
90
88
  """
91
89
  # Remove registries is called whenever an improperly formatted plugin or
92
90
  # package is encountered. This is not called until all errors have been
@@ -110,7 +108,9 @@ def remove_registries(namespace, plugin_packages):
110
108
  remove(json_plug_path)
111
109
 
112
110
 
113
- def registry_sanity_check(plugin_packages, save_type, namespace):
111
+ def registry_sanity_check(
112
+ plugin_packages: list, save_type: str, namespace: str
113
+ ) -> None:
114
114
  """Check that each plugin package registry has no duplicate lowest depth entries.
115
115
 
116
116
  If it does, raise a `PluginRegistryError` for that specific package, then remove all
@@ -120,19 +120,15 @@ def registry_sanity_check(plugin_packages, save_type, namespace):
120
120
 
121
121
  Parameters
122
122
  ----------
123
- plugin_packages: list EntryPoints
123
+ plugin_packages : list EntryPoints
124
124
  A list of EntryPoints pointing to each installed package --> ie.
125
125
  `[EntryPoint(name='pluginify', value='pluginify',
126
126
  group='pluginify.plugin_packages')]`
127
- save_type: str
127
+ save_type : str
128
128
  The file format to save to `[json, yaml]`
129
129
 
130
- Returns
131
- -------
132
- No returns
133
-
134
- Exceptions
135
- ----------
130
+ Raises
131
+ ------
136
132
  PluginRegistryError
137
133
  If `error_message` has contents, then raise PluginRegistryError(error_message).
138
134
  The `error_message` string will collect and report on all errors within
@@ -262,7 +258,9 @@ def registry_sanity_check(plugin_packages, save_type, namespace):
262
258
  raise PluginRegistryError(error_message)
263
259
 
264
260
 
265
- def check_plugin_exists(package, plugins, interface_name, plugin_name, relpath):
261
+ def check_plugin_exists(
262
+ package: str, plugins: dict, interface_name: str, plugin_name: str, relpath: str
263
+ ) -> str:
266
264
  """Check if plugin already exists. If it does raise a `PluginRegistryError`.
267
265
 
268
266
  Note this only checks for duplicate plugins within a single plugin package.
@@ -271,14 +269,16 @@ def check_plugin_exists(package, plugins, interface_name, plugin_name, relpath):
271
269
 
272
270
  Parameters
273
271
  ----------
274
- package: str
272
+ package : str
275
273
  The package being tested against
276
- plugins: dict
274
+ plugins : dict
277
275
  A dictionary object of all installed plugins in the current plugin package.
278
- interface_name: str
276
+ interface_name : str
279
277
  A string representing the interface being checked against
280
- plugin_name: str
278
+ plugin_name : str
281
279
  A string representing the name of the plugin within the interface
280
+ relpath : str
281
+ The relative path to the plugin being checked.
282
282
 
283
283
  Returns
284
284
  -------
@@ -299,21 +299,19 @@ def check_plugin_exists(package, plugins, interface_name, plugin_name, relpath):
299
299
  return ""
300
300
 
301
301
 
302
- def write_registered_plugins(pkg_dir, plugins, save_type):
302
+ def write_registered_plugins(
303
+ pkg_dir: str | Path, plugins: dict, save_type: str
304
+ ) -> None:
303
305
  """Write dictionary of all plugins available from installed packages.
304
306
 
305
307
  Parameters
306
308
  ----------
307
- pkg_dir: str
309
+ pkg_dir : str
308
310
  Path in which to write registered_plugins
309
- plugins: dict
311
+ plugins : dict
310
312
  A dictionary object of all installed package plugins
311
- save_type: str
313
+ save_type : str
312
314
  The file format to save to [json, yaml]
313
-
314
- Returns
315
- -------
316
- No returns, file written to `pkg_dir`
317
315
  """
318
316
  if save_type == "yaml":
319
317
  reg_plug_abspath = os_join(pkg_dir, "registered_plugins.yaml")
@@ -323,21 +321,32 @@ def write_registered_plugins(pkg_dir, plugins, save_type):
323
321
  # Write to a temporary file and move it into place, so that other processes
324
322
  # reading the registry (for example parallel test workers) never see a
325
323
  # partially written file.
326
- tmp_abspath = f"{reg_plug_abspath}.{getpid()}.tmp"
324
+ tmp_abspath = None
327
325
  try:
328
- with open(tmp_abspath, "w") as plugin_registry:
326
+ with tempfile.NamedTemporaryFile(
327
+ mode="w",
328
+ dir=dirname(reg_plug_abspath),
329
+ suffix=".tmp",
330
+ delete=False,
331
+ ) as plugin_registry:
332
+ tmp_abspath = plugin_registry.name
329
333
  if save_type == "yaml":
330
334
  yaml.safe_dump(plugins, plugin_registry, default_flow_style=False)
331
335
  else:
332
336
  json.dump(plugins, plugin_registry, indent=4)
337
+
333
338
  replace(tmp_abspath, reg_plug_abspath)
334
- except BaseException:
335
- if exists(tmp_abspath):
336
- remove(tmp_abspath)
337
- raise
339
+ finally:
340
+ if tmp_abspath is not None:
341
+ try:
342
+ remove(tmp_abspath)
343
+ except FileNotFoundError:
344
+ pass
338
345
 
339
346
 
340
- def create_plugin_registries(plugin_packages, save_type, namespace):
347
+ def create_plugin_registries(
348
+ plugin_packages: list, save_type: str, namespace: str
349
+ ) -> None:
341
350
  """Generate all plugin paths associated with every installed packages.
342
351
 
343
352
  These paths include text plugins, class_based plugins
@@ -347,13 +356,13 @@ def create_plugin_registries(plugin_packages, save_type, namespace):
347
356
 
348
357
  Parameters
349
358
  ----------
350
- plugin_packages: list EntryPoints
359
+ plugin_packages : list EntryPoints
351
360
  A list of EntryPoints pointing to each installed package --> ie.
352
361
  [EntryPoint(name='pluginify', value='pluginify',
353
362
  group='pluginify.plugin_packages'), ...]
354
- save_type: str
363
+ save_type : str
355
364
  The file format to save to [json, yaml]
356
- namespace: str
365
+ namespace : str
357
366
  Namespace that your plugin packages fall under. The argument parser defaults
358
367
  this value to 'pluginify.plugin_packages', but a user can create separate
359
368
  namespaces if developing interfaces outside of pluginify.
@@ -361,8 +370,8 @@ def create_plugin_registries(plugin_packages, save_type, namespace):
361
370
  # It appears when there is *.egg-info directory, it picks that package up
362
371
  # twice in the list. If the same package path exists twice, only keep one
363
372
  # of them. This appears to be a bug with Python 3.9 entry points.
364
- pkg_dirs = []
365
- unique_package_entry_points = []
373
+ pkg_dirs: list = []
374
+ unique_package_entry_points: list = []
366
375
  # Note we only use ep.value (resources.files finds the actual plugins),
367
376
  # so we do not need to worry about saving the "wrong" package here.
368
377
  # We are actually looping through all the files in each package, so
@@ -381,7 +390,7 @@ def create_plugin_registries(plugin_packages, save_type, namespace):
381
390
  for pkg in unique_package_entry_points:
382
391
  # This is passed by reference and populated with each call to parse
383
392
  # plugin packages.
384
- plugins = {
393
+ plugins: dict = {
385
394
  "text_based": {},
386
395
  "yaml_based": {},
387
396
  "class_based": {},
@@ -398,7 +407,7 @@ def create_plugin_registries(plugin_packages, save_type, namespace):
398
407
  # that holds all plugins, and the package_schema entry point directly
399
408
  # to the directory that holds all scheam, so the subdirectories are
400
409
  # not hard coded here in the create_plugin_registries code.
401
- pkg_plugin_path = resources.files(package) / "plugins"
410
+ pkg_plugin_path = Path(str(resources.files(package) / "plugins"))
402
411
  pkg_dir = str(resources.files(package))
403
412
  # Generate a path to the parent directory where we should write the plugin
404
413
  # registry file for this package to.
@@ -455,7 +464,13 @@ def create_plugin_registries(plugin_packages, save_type, namespace):
455
464
  registry_sanity_check(unique_package_entry_points, save_type, namespace)
456
465
 
457
466
 
458
- def parse_plugin_paths(plugin_paths, package, package_dir, plugins, namespace):
467
+ def parse_plugin_paths(
468
+ plugin_paths: dict,
469
+ package: str,
470
+ package_dir: str,
471
+ plugins: dict,
472
+ namespace: str,
473
+ ) -> str:
459
474
  """Parse the plugin_paths provided from the current installed package.
460
475
 
461
476
  Then, add them to the plugins dictionary based on the path of the plugin.
@@ -464,22 +479,22 @@ def parse_plugin_paths(plugin_paths, package, package_dir, plugins, namespace):
464
479
 
465
480
  Parameters
466
481
  ----------
467
- plugin_paths: dict
482
+ plugin_paths : dict
468
483
  A dictionary of filepaths, with keys referring to the type of plugin
469
- package: str
484
+ package : str
470
485
  The current package being parsed
471
- package_dir: str
486
+ package_dir : str
472
487
  The path to the current package (for determining relative paths)
473
- plugins: dict
488
+ plugins : dict
474
489
  A dictionary object of all installed package plugins
475
- namespace: str
490
+ namespace : str
476
491
  Namespace that your plugin packages fall under. The argument parser defaults
477
492
  this value to 'pluginify.plugin_packages', but a user can create separate
478
493
  namespaces if developing interfaces outside of pluginify.
479
494
 
480
495
  Returns
481
496
  -------
482
- error_message: str
497
+ error_message : str
483
498
  String containing informative error messages from any plugins that
484
499
  were improperly formatted. An exception will be raised at the
485
500
  very end if error_message is not the empty string - this allows
@@ -531,27 +546,29 @@ def parse_plugin_paths(plugin_paths, package, package_dir, plugins, namespace):
531
546
  return error_message
532
547
 
533
548
 
534
- def add_yaml_plugin(filepath, relpath, package, plugins, namespace):
549
+ def add_yaml_plugin(
550
+ filepath: str, relpath: str, package: str, plugins: dict, namespace: str
551
+ ) -> str:
535
552
  """Add the yaml plugin associated with the filepaths and package to plugins.
536
553
 
537
554
  Parameters
538
555
  ----------
539
- filepath: str
556
+ filepath : str
540
557
  The path of the plugin derived from resouces.files(package) / plugin
541
- relpath: str
558
+ relpath : str
542
559
  The relative path to the filepath provided
543
- package: str
560
+ package : str
544
561
  The current package being parsed
545
- plugins: dict
562
+ plugins : dict
546
563
  A dictionary object of all installed package plugins
547
- namespace: str
564
+ namespace : str
548
565
  Namespace that your plugin packages fall under. The argument parser defaults
549
566
  this value to 'pluginify.plugin_packages', but a user can create separate
550
567
  namespaces if developing interfaces outside of pluginify.
551
568
 
552
569
  Returns
553
570
  -------
554
- error_message: str
571
+ error_message : str
555
572
  String containing informative error messages from any plugins that
556
573
  were improperly formatted. An exception will be raised at the
557
574
  very end if error_message is not the empty string - this allows
@@ -686,7 +703,7 @@ def add_yaml_plugin(filepath, relpath, package, plugins, namespace):
686
703
  # if not family:
687
704
  # family = plugins["product_defaults"][pd]["family"]
688
705
  plugins[interface_name][subplg_source][subplg_product] = {
689
- "docstring": str(format_docstring(docstring)),
706
+ "docstring": str(format_docstring(docstring or "")),
690
707
  "family": family,
691
708
  "interface": interface_module.name,
692
709
  "package": plugin["package"],
@@ -727,21 +744,21 @@ def add_yaml_plugin(filepath, relpath, package, plugins, namespace):
727
744
  return error_message
728
745
 
729
746
 
730
- def add_text_plugin(package, relpath, plugins):
747
+ def add_text_plugin(package: str, relpath: str, plugins: dict) -> str:
731
748
  """Add all text plugins into plugin registries.
732
749
 
733
750
  Parameters
734
751
  ----------
735
- package: str
752
+ package : str
736
753
  The current package being parsed
737
- relpath: str
754
+ relpath : str
738
755
  The relpath path to the text plugin
739
- plugins: dict
756
+ plugins : dict
740
757
  A dictionary object of all installed package plugins
741
758
 
742
759
  Returns
743
760
  -------
744
- error_message: str
761
+ error_message : str
745
762
  String containing informative error messages from any plugins that
746
763
  were improperly formatted. An exception will be raised at the
747
764
  very end if error_message is not the empty string - this allows
@@ -770,8 +787,12 @@ def add_text_plugin(package, relpath, plugins):
770
787
 
771
788
 
772
789
  def collect_module_plugin_metadata(
773
- module, module_name, package, relpath, error_message
774
- ):
790
+ module: Any,
791
+ module_name: str,
792
+ package: str,
793
+ relpath: str,
794
+ error_message: str,
795
+ ) -> dict | str:
775
796
  """Collect metadata for the module-based plugin for the plugin registry.
776
797
 
777
798
  Metadata necessary to collect here includes the interface of the plugin, the
@@ -783,27 +804,27 @@ def collect_module_plugin_metadata(
783
804
 
784
805
  Parameters
785
806
  ----------
786
- module: ModuleType
787
- - The object of the module which has been loaded in realtime. This should have
788
- top level attributes such as 'interface', 'name', 'family' and so on.
789
- module_name: str
790
- - The name of the .py file.
791
- package: str
792
- - The name of the package this plugin comes from
793
- relpath: str
794
- - The relative path to the plugin found in 'package'
795
- error_message: str
796
- - The current state of errors found during building the registry, stored as a
797
- string.
807
+ module : ModuleType
808
+ The object of the module which has been loaded in realtime. This should have
809
+ top level attributes such as 'interface', 'name', 'family' and so on.
810
+ module_name : str
811
+ The name of the .py file.
812
+ package : str
813
+ The name of the package this plugin comes from
814
+ relpath : str
815
+ The relative path to the plugin found in 'package'
816
+ error_message : str
817
+ The current state of errors found during building the registry, stored as a
818
+ string.
798
819
 
799
820
  Returns
800
821
  -------
801
- metadata: dict
802
- - A dictionary of metadata for the provided plugin class to store in the plugin
803
- registry.
804
- error_message: str
805
- - The current state of errors found during building the registry, stored as a
806
- string.
822
+ metadata : dict
823
+ A dictionary of metadata for the provided plugin class to store in the plugin
824
+ registry.
825
+ error_message : str
826
+ The current state of errors found during building the registry, stored as a
827
+ string.
807
828
  """
808
829
  # Try to get "interface" variable from the module. This is required
809
830
  # on ALL files within the python module based plugins directory, to
@@ -874,7 +895,7 @@ def collect_module_plugin_metadata(
874
895
  return metadata
875
896
 
876
897
 
877
- def collect_class_plugin_metadata(plugin_class):
898
+ def collect_class_plugin_metadata(plugin_class, package, relpath, error_message):
878
899
  """Collect metadata linked to the class plugin in 'module' for the plugin registry.
879
900
 
880
901
  Metadata necessary to collect here includes the interface of the plugin, the
@@ -883,24 +904,60 @@ def collect_class_plugin_metadata(plugin_class):
883
904
 
884
905
  Parameters
885
906
  ----------
886
- plugin_class: Object, default=None
887
- - The plugin class from 'module' to collect metadata from. If None, this
888
- function will attempt to locate the correct plugin class from the provided
889
- module.
907
+ plugin_class : Object, default=None
908
+ The plugin class from 'module' to collect metadata from. If None, this
909
+ function will attempt to locate the correct plugin class from the provided
910
+ module.
890
911
 
891
912
  Returns
892
913
  -------
893
- metadata: dict
894
- - A dictionary of metadata for the provided plugin class to store in the plugin
895
- registry.
914
+ metadata : dict
915
+ A dictionary of metadata for the provided plugin class to store in the plugin
916
+ registry.
896
917
  """
897
- # NOTE: Probably should add some attribute checks here as
898
- # 'collect_module_plugin_metadata' does
918
+ error_occurred = False
919
+
920
+ try:
921
+ family = plugin_class.family
922
+ except AttributeError:
923
+ if not getattr(plugin_class, "data_tree"):
924
+ error_occurred = True
925
+ error_message += (
926
+ f"Error: missing 'family' attribute in non-data-tree class based "
927
+ f"plugin found at '{relpath}' in package '{package}'. This attribute "
928
+ "must be specified for non-data-tree class-based plugins."
929
+ )
930
+ else:
931
+ family = "N/A: data tree based plugin"
932
+
933
+ try:
934
+ name = plugin_class.name
935
+ except AttributeError:
936
+ error_occurred = True
937
+ error_message += (
938
+ f"Error: missing 'name' attribute in class based "
939
+ f"plugin found at '{relpath}' in package '{package}'. This attribute must "
940
+ "be specified in class-based plugins."
941
+ )
942
+
943
+ try:
944
+ interface = plugin_class.interface
945
+ except AttributeError:
946
+ error_occurred = True
947
+ error_message += (
948
+ f"Error: missing 'interface' attribute in class based "
949
+ f"plugin found at '{relpath}' in package '{package}'. This attribute must "
950
+ "be specified in class-based plugins."
951
+ )
952
+
953
+ if error_occurred:
954
+ return error_message
955
+
899
956
  metadata = {
900
957
  "docstring": format_docstring(plugin_class.__doc__),
901
- "name": plugin_class.name,
902
- "family": plugin_class.family,
903
- "interface": plugin_class.interface,
958
+ "name": name,
959
+ "family": family,
960
+ "interface": interface,
904
961
  "plugin_type": "class_based",
905
962
  "signature": str(signature(plugin_class.call)),
906
963
  "is_derived_from_module": False,
@@ -909,7 +966,7 @@ def collect_class_plugin_metadata(plugin_class):
909
966
  return metadata
910
967
 
911
968
 
912
- def add_class_plugin(package, relpath, plugins):
969
+ def add_class_plugin(package: str, relpath: str, plugins: dict) -> str:
913
970
  """Add the class-based plugin associated with the filepaths and package to plugins.
914
971
 
915
972
  NOTE: This function will work for 'legacy' module-based plugins that are dynamically
@@ -920,16 +977,16 @@ def add_class_plugin(package, relpath, plugins):
920
977
 
921
978
  Parameters
922
979
  ----------
923
- package: str
980
+ package : str
924
981
  The current package being parsed
925
- relpath: str
982
+ relpath : str
926
983
  The relpath path to the class-based plugin
927
- plugins: dict
984
+ plugins : dict
928
985
  A dictionary object of all installed package plugins
929
986
 
930
987
  Returns
931
988
  -------
932
- error_message: str
989
+ error_message : str
933
990
  String containing informative error messages from any plugins that
934
991
  were improperly formatted. An exception will be raised at the
935
992
  very end if error_message is not the empty string - this allows
@@ -960,7 +1017,11 @@ def add_class_plugin(package, relpath, plugins):
960
1017
  module_path = f"{package}.{module_path}"
961
1018
  abspath = resources.files(package) / relpath
962
1019
 
963
- spec = util.spec_from_file_location(module_path, abspath)
1020
+ spec = util.spec_from_file_location(module_path, str(abspath))
1021
+ if spec is None or spec.loader is None:
1022
+ raise PluginRegistryError(
1023
+ f"Could not create a module spec for '{module_path}' at '{abspath}'."
1024
+ )
964
1025
  module = util.module_from_spec(spec)
965
1026
  # Attempting importing module, catch ImportError
966
1027
  # We have to fix these to be able to import the module to
@@ -1012,7 +1073,14 @@ def add_class_plugin(package, relpath, plugins):
1012
1073
  error_message += str(plugin_class_error_message)
1013
1074
  return error_message
1014
1075
 
1015
- plugin_metadata = collect_class_plugin_metadata(PLUGIN_CLASS)
1076
+ plugin_metadata = collect_class_plugin_metadata(
1077
+ PLUGIN_CLASS, package, relpath, error_message
1078
+ )
1079
+ if isinstance(plugin_metadata, str):
1080
+ # Error message was returned instead of a metadata dictionary. Just return
1081
+ # that message now.
1082
+ return plugin_metadata
1083
+
1016
1084
  interface_name = plugin_metadata["interface"]
1017
1085
  name = plugin_metadata.pop("name")
1018
1086
 
pluginify/errors.py CHANGED
@@ -1,16 +1,24 @@
1
1
  # # # This source code is subject to the license referenced at
2
2
  # # # https://github.com/NRLMMD-GEOIPS.
3
3
 
4
- """Pluginify error module."""
4
+ """Exceptions raised by the pluginify package.
5
+
6
+ Provides two error classes:
7
+
8
+ - ``PluginError`` — raised when an individual plugin is malformed, missing
9
+ required attributes, or otherwise invalid.
10
+ - ``PluginRegistryError`` — raised when a plugin registry itself is missing,
11
+ corrupt, or contains duplicate/conflicting entries across packages.
12
+ """
5
13
 
6
14
 
7
15
  class PluginError(Exception):
8
- """Exception to be raised when there is an error in a plugin."""
16
+ """Exception raised for errors related to an individual plugin."""
9
17
 
10
18
  pass
11
19
 
12
20
 
13
21
  class PluginRegistryError(Exception):
14
- """Exception to be raised when there is an error in a plugin registry."""
22
+ """Exception raised for errors related to a plugin registry."""
15
23
 
16
24
  pass
@@ -1,7 +1,16 @@
1
1
  # # # This source code is subject to the license referenced at
2
2
  # # # https://github.com/NRLMMD-GEOIPS.
3
3
 
4
- """Initialization module for pluginify interfaces."""
4
+ """Interface discovery and initialization module for pluginify.
5
+
6
+ Imports all available class-based and YAML-based interface instances so that
7
+ other parts of the pluginify ecosystem can discover them via
8
+ ``from pluginify.interfaces import <interface_name>``.
9
+
10
+ The ``class_based_interfaces`` and ``yaml_based_interfaces`` lists are used by
11
+ ``PluginRegistryValidator.validate_registry_interfaces`` to validate that
12
+ registry entries reference known interface names.
13
+ """
5
14
 
6
15
  from pluginify.interfaces.class_based.data_modifiers import data_modifiers # noqa: F401
7
16
  from pluginify.interfaces.yaml_based.configs import configs # noqa: F401