pluginify 1.0.0.post16.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.
@@ -3,18 +3,24 @@
3
3
 
4
4
  """Base classes for interfaces, plugins, and plugin validation machinery."""
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  import abc
7
9
  from glob import glob
8
10
  from importlib.resources import files
9
11
  import inspect
10
12
  import logging
11
13
  from os.path import basename
14
+ from typing import TYPE_CHECKING, Any, cast
12
15
 
13
16
  from jsonschema.exceptions import ValidationError
14
17
 
15
18
  from pluginify.config import REBUILD_REGISTRIES
16
19
  from pluginify.errors import PluginError
17
20
 
21
+ if TYPE_CHECKING:
22
+ from pluginify.plugin_registry import PluginRegistry
23
+
18
24
  LOG = logging.getLogger(__name__)
19
25
 
20
26
 
@@ -41,7 +47,7 @@ class BaseInterface(abc.ABC):
41
47
  # This can be overridden by setting them in child interface classes
42
48
  apiVersion = "pluginify/v1"
43
49
 
44
- def __new__(cls):
50
+ def __new__(cls) -> BaseInterface:
45
51
  """Plugin interface new method."""
46
52
  if not hasattr(cls, "name") or not cls.name:
47
53
  raise AttributeError(
@@ -54,7 +60,7 @@ class BaseInterface(abc.ABC):
54
60
  return super(BaseInterface, cls).__new__(cls)
55
61
 
56
62
  @property
57
- def namespace(self):
63
+ def namespace(self) -> str:
58
64
  """Default namespace used for the plugin registry associated with this class.
59
65
 
60
66
  By default, we use 'pluginify.plugin_packages' as the namespace for interface
@@ -69,7 +75,7 @@ class BaseInterface(abc.ABC):
69
75
  return self._namespace
70
76
 
71
77
  @property
72
- def plugin_registry(self):
78
+ def plugin_registry(self) -> PluginRegistry:
73
79
  """The plugin registry associated with this interface.
74
80
 
75
81
  By default, we use 'pluginify.plugin_packages' as the namespace for interface
@@ -89,17 +95,17 @@ class BaseInterface(abc.ABC):
89
95
 
90
96
  Parameters
91
97
  ----------
92
- name: str or tuple(str)
93
- - The name of the yaml-based plugin. Either a single string or a tuple of
94
- strings for product plugins.
95
- rebuild_registries: bool (default=True)
96
- - Whether or not to rebuild the registries if get_plugin fails. If set to
97
- true and get_plugin fails, rebuild the plugin registry, call then call
98
- get_plugin once more with rebuild_registries toggled off, so it only gets
99
- rebuilt once.
100
- - By default, the value of rebuild_registries is set to True if not
101
- explicitly set to False as an configuration variable under the name
102
- `REBUILD_REGISTRIES` in `~/.config/pluginify/config.yaml`.
98
+ name : str or tuple(str)
99
+ The name of the yaml-based plugin. Either a single string or a tuple of
100
+ strings for product plugins.
101
+ rebuild_registries : bool (default=True)
102
+ Whether or not to rebuild the registries if get_plugin fails. If set to
103
+ true and get_plugin fails, rebuild the plugin registry, call then call
104
+ get_plugin once more with rebuild_registries toggled off, so it only gets
105
+ rebuilt once.
106
+ By default, the value of rebuild_registries is set to True if not
107
+ explicitly set to False as an configuration variable under the name
108
+ `REBUILD_REGISTRIES` in `~/.config/pluginify/config.yaml`.
103
109
  """
104
110
  pass
105
111
 
@@ -108,7 +114,7 @@ class BaseInterface(abc.ABC):
108
114
  """Abstract function for retrieving all plugins under a certain interface."""
109
115
  pass
110
116
 
111
- def get_plugin_metadata(self, name):
117
+ def get_plugin_metadata(self, name: str | tuple[str, str]) -> dict:
112
118
  """Retrieve a plugin's metadata.
113
119
 
114
120
  Where the metadata of the plugin matches the plugin's corresponding entry in the
@@ -116,13 +122,13 @@ class BaseInterface(abc.ABC):
116
122
 
117
123
  Parameters
118
124
  ----------
119
- name: str or tuple(str)
120
- - The name of the plugin whose metadata we want.
125
+ name : str or tuple(str)
126
+ The name of the plugin whose metadata we want.
121
127
 
122
128
  Returns
123
129
  -------
124
- metadata: dict
125
- - A dictionary of metadata for the requested plugin.
130
+ dict
131
+ A dictionary of metadata for the requested plugin.
126
132
  """
127
133
  return self.plugin_registry.get_plugin_metadata(self, name)
128
134
 
@@ -130,11 +136,11 @@ class BaseInterface(abc.ABC):
130
136
  class BaseYamlPlugin(dict):
131
137
  """Base class for YAML plugins."""
132
138
 
133
- def __init__(self, *args, **kwargs):
139
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
134
140
  """Class BaseYamlPlugin init method."""
135
141
  super().__init__(*args, **kwargs)
136
142
 
137
- def __repr__(self):
143
+ def __repr__(self) -> str:
138
144
  """Class BaseYamlPlugin repr method."""
139
145
  val = super().__repr__()
140
146
  return f"{self.__class__.__name__}({val})"
@@ -161,13 +167,11 @@ class BaseYamlInterface(BaseInterface):
161
167
  name = "BaseYamlInterface"
162
168
  use_pydantic = False
163
169
 
164
- def __new__(cls):
170
+ def __new__(cls) -> BaseYamlInterface:
165
171
  """YAML plugin interface new method."""
166
- cls = super(BaseInterface, cls).__new__(cls)
172
+ return cast(BaseYamlInterface, super(BaseInterface, cls).__new__(cls))
167
173
 
168
- return cls
169
-
170
- def __init__(self):
174
+ def __init__(self) -> None:
171
175
  """YAML plugin interface init method."""
172
176
  try:
173
177
  self.supported_families = [
@@ -181,11 +185,21 @@ class BaseYamlInterface(BaseInterface):
181
185
  # fix and will be removed entirely once families are removed.
182
186
  self.supported_families = ["standard"]
183
187
 
184
- def _create_registered_plugin_names(self, yaml_plugin):
188
+ def _create_registered_plugin_names(self, yaml_plugin: dict) -> list[str]:
185
189
  """Create a plugin name for plugin registry.
186
190
 
187
191
  Some interfaces need to override this (e.g. products) because they
188
192
  need a more complex name for retrieval.
193
+
194
+ Parameters
195
+ ----------
196
+ yaml_plugin : dict
197
+ The YAML plugin dictionary.
198
+
199
+ Returns
200
+ -------
201
+ list of str
202
+ A list of registered plugin names derived from the plugin.
189
203
  """
190
204
  return [yaml_plugin["name"]]
191
205
 
@@ -200,7 +214,9 @@ class BaseYamlInterface(BaseInterface):
200
214
  pass
201
215
 
202
216
  @classmethod
203
- def _plugin_yaml_to_obj(cls, name, yaml_plugin, obj_attrs={}):
217
+ def _plugin_yaml_to_obj(
218
+ cls, name: str, yaml_plugin: dict, obj_attrs: dict | None = None
219
+ ) -> BaseYamlPlugin:
204
220
  """Convert a yaml plugin to an object.
205
221
 
206
222
  Convert the passed YAML plugin into an object and return it. The returned
@@ -222,6 +238,8 @@ class BaseYamlInterface(BaseInterface):
222
238
  - name: The name of the plugin which must be unique within the interface.
223
239
  - docstring: A string to be used as the object's docstring.
224
240
  """
241
+ if obj_attrs is None:
242
+ obj_attrs = {}
225
243
  obj_attrs["id"] = name
226
244
  obj_attrs["yaml"] = yaml_plugin
227
245
 
@@ -239,7 +257,7 @@ class BaseYamlInterface(BaseInterface):
239
257
  obj_attrs[attr] = yaml_plugin[attr]
240
258
  # This should be removed once we fully switch to pydantic models
241
259
  except TypeError:
242
- yaml_plugin = yaml_plugin.model_dump()
260
+ yaml_plugin = cast(Any, yaml_plugin).model_dump()
243
261
  obj_attrs[attr] = yaml_plugin[attr]
244
262
  except KeyError:
245
263
  missing.append(attr)
@@ -257,13 +275,18 @@ class BaseYamlInterface(BaseInterface):
257
275
  plugin_base_class = BaseYamlPlugin
258
276
  if hasattr(cls, "plugin_class") and cls.plugin_class:
259
277
  plugin_base_class = cls.plugin_class
260
- return type(plugin_type, (plugin_base_class,), obj_attrs)(yaml_plugin)
278
+ return cast(
279
+ BaseYamlPlugin,
280
+ type(plugin_type, (plugin_base_class,), obj_attrs)(yaml_plugin),
281
+ )
261
282
 
262
- def __repr__(self):
283
+ def __repr__(self) -> str:
263
284
  """Plugin interface repr method."""
264
285
  return f"{self.__class__.__name__}()"
265
286
 
266
- def get_plugin(self, name, rebuild_registries=None):
287
+ def get_plugin(
288
+ self, name: str | tuple[str, str], rebuild_registries: bool | None = None
289
+ ) -> Any:
267
290
  """Get a plugin by its name.
268
291
 
269
292
  This default method can be overridden to provide different search
@@ -273,42 +296,59 @@ class BaseYamlInterface(BaseInterface):
273
296
 
274
297
  Parameters
275
298
  ----------
276
- name: str or tuple(str)
277
- - The name of the yaml-based plugin. Either a single string or a tuple of
278
- strings for product plugins.
279
- rebuild_registries: bool (default=None)
280
- - Whether or not to rebuild the registries if get_plugin fails. If set to
281
- None, default to the value of
282
- `~/.config/pluginify/config.yaml:REBUILD_REGISTRIES`. If that
283
- configuration variable is not set, we default to True.
284
- If specified, use the input value of rebuild_registries, which should be a
285
- boolean value. If rebuild registries is true and get_plugin fails, rebuild
286
- the plugin registry, call then call get_plugin once more with
287
- rebuild_registries toggled off, so it only gets rebuilt once.
299
+ name : str or tuple(str)
300
+ The name of the yaml-based plugin. Either a single string or a tuple of
301
+ strings for product plugins.
302
+ rebuild_registries : bool (default=None)
303
+ Whether or not to rebuild the registries if get_plugin fails. If set to
304
+ None, default to the value of
305
+ `~/.config/pluginify/config.yaml:REBUILD_REGISTRIES`. If that
306
+ configuration variable is not set, we default to True.
307
+ If specified, use the input value of rebuild_registries, which should be a
308
+ boolean value. If rebuild registries is true and get_plugin fails, rebuild
309
+ the plugin registry, call then call get_plugin once more with
310
+ rebuild_registries toggled off, so it only gets rebuilt once.
288
311
  """
289
312
  return self.plugin_registry.get_yaml_plugin(self, name, rebuild_registries)
290
313
 
291
- def get_plugins(self):
314
+ def get_plugins(self) -> list:
292
315
  """Retrieve all yaml plugin objects."""
293
316
  return self.plugin_registry.get_yaml_plugins(self)
294
317
 
295
- def plugin_is_valid(self, name):
296
- """Plugin is valid method."""
318
+ def plugin_is_valid(self, name: str | tuple[str, str]) -> bool:
319
+ """Check if a plugin is valid by name.
320
+
321
+ Parameters
322
+ ----------
323
+ name : str or tuple(str)
324
+ The name of the plugin to validate.
325
+
326
+ Returns
327
+ -------
328
+ bool
329
+ True if the plugin is valid, False otherwise.
330
+ """
297
331
  try:
298
332
  self.get_plugin(name)
299
333
  return True
300
334
  except ValidationError:
301
335
  return False
302
336
 
303
- def plugins_all_valid(self):
304
- """Plugins all valid method."""
337
+ def plugins_all_valid(self) -> bool:
338
+ """Check if all plugins are valid.
339
+
340
+ Returns
341
+ -------
342
+ bool
343
+ True if all plugins are valid, False otherwise.
344
+ """
305
345
  try:
306
346
  self.get_plugins()
307
347
  return True
308
348
  except ValidationError:
309
349
  return False
310
350
 
311
- def test_interface(self):
351
+ def test_interface(self) -> dict:
312
352
  """Test interface method.
313
353
 
314
354
  Note this is currently only called via the
@@ -319,7 +359,7 @@ class BaseYamlInterface(BaseInterface):
319
359
  plugins = self.get_plugins()
320
360
  all_valid = self.plugins_all_valid()
321
361
  family_list = []
322
- plugin_ids = {}
362
+ plugin_ids: dict = {}
323
363
  for plugin in plugins:
324
364
  # TODO: Refactor to remove this `if` block after full migration from JSON
325
365
  # to Pydantic schemas.
@@ -342,7 +382,7 @@ class BaseYamlInterface(BaseInterface):
342
382
  plugin_ids[plugin_family] = []
343
383
  plugin_ids[plugin_family].append(plugin_name)
344
384
 
345
- output = {
385
+ output: dict[str, Any] = {
346
386
  "all_valid": all_valid,
347
387
  "by_family": plugin_ids,
348
388
  "validity_check": {},
@@ -376,17 +416,20 @@ class BaseClassInterface(BaseInterface):
376
416
  interface_type = "class_based"
377
417
  name = "BaseClassInterface"
378
418
  required_args: dict[str, list[str]] = {}
419
+ required_kwargs: dict[str, list[str]] = {}
379
420
 
380
- def __repr__(self):
421
+ def __repr__(self) -> str:
381
422
  """Plugin interface repr method."""
382
423
  return f"{self.__class__.__name__}()"
383
424
 
384
- def __init__(self):
425
+ def __init__(self) -> None:
385
426
  """Initialize module plugin interface."""
386
427
  self.supported_families = list(self.required_args.keys())
387
428
 
388
429
  @classmethod
389
- def _plugin_module_to_obj(cls, name, module, obj_attrs={}):
430
+ def _plugin_module_to_obj(
431
+ cls, name: str, module, obj_attrs: dict | None = None
432
+ ) -> Any:
390
433
  """Convert a module plugin to an object.
391
434
 
392
435
  Convert the passed module plugin into an object and return it. The returned
@@ -414,19 +457,39 @@ class BaseClassInterface(BaseInterface):
414
457
  Parameters
415
458
  ----------
416
459
  module : module
417
- The imported plugin module.
460
+ The imported plugin module.
418
461
  obj_attrs : dict, optional
419
- Additional attributes to be assigned to the plugin object.
462
+ Additional attributes to be assigned to the plugin object.
420
463
 
421
464
  Returns
422
465
  -------
423
- An object of type ``<interface>InterfacePlugin`` where ``<interface>`` is the
424
- name of the interface that the desired plugin belongs to.
466
+ <interface>InterfacePlugin
467
+ An object of type ``<interface>InterfacePlugin`` where ``<interface>``
468
+ is the name of the interface that the desired plugin belongs to.
425
469
  """
470
+ if obj_attrs is None:
471
+ obj_attrs = {}
426
472
  obj_attrs["id"] = name
427
473
 
428
474
  missing = []
429
- for attr in ["interface", "family", "name"]:
475
+ required_attrs = ["interface", "name"]
476
+
477
+ # Always require 'plugin_class' from each class-based interface
478
+ # This is enforced in the 'test_interfaces' unit test.
479
+ if not hasattr(cls, "plugin_class") or cls.plugin_class is None:
480
+ raise PluginError(
481
+ f"Error: interface '{cls.name}' is missing required "
482
+ "attribute 'plugin_class'. Please create a base class plugin for this "
483
+ "interface and assign that object to the 'plugin_class' attribute of "
484
+ "this interface before continuing."
485
+ )
486
+
487
+ # If the base plugin class doesn't support data tree native plugins, make sure
488
+ # to require the family attribute
489
+ if not getattr(cls.plugin_class, "data_tree", False):
490
+ required_attrs += ["family"]
491
+
492
+ for attr in required_attrs:
430
493
  try:
431
494
  obj_attrs[attr] = getattr(module, attr)
432
495
  except AttributeError:
@@ -459,55 +522,46 @@ class BaseClassInterface(BaseInterface):
459
522
  plugin_interface_name = obj_attrs["interface"].title().replace("_", "")
460
523
  plugin_type = f"{plugin_interface_name}Plugin"
461
524
 
462
- # Always require 'plugin_class' from each class-based interface
463
- # This is enforced in the 'test_interfaces' unit test.
464
- if not hasattr(cls, "plugin_class") or cls.plugin_class is None:
465
- raise PluginError(
466
- f"Error: interface '{obj_attrs['interface']}' is missing required "
467
- "attribute 'plugin_class'. Please create a base class plugin for this "
468
- "interface and assign that object to the 'plugin_class' attribute of "
469
- "this interface before continuing."
470
- )
471
-
472
525
  plugin_base_class = cls.plugin_class
473
526
 
474
527
  # Create an object of type ``plugin_type`` with attributes from ``obj_attrs``
475
528
  return type(plugin_type, (plugin_base_class,), obj_attrs)(module)
476
529
 
477
- def get_plugin(self, name, rebuild_registries=None):
530
+ def get_plugin(self, name: str, rebuild_registries: bool | None = None) -> Any:
478
531
  """Retrieve a plugin from this interface by name.
479
532
 
480
533
  Parameters
481
534
  ----------
482
535
  name : str
483
- - The name the desired plugin.
484
- rebuild_registries: bool (default=None)
485
- - Whether or not to rebuild the registries if get_plugin fails. If set to
486
- None, default to the value of
487
- `~/.config/pluginify/config.yaml:REBUILD_REGISTRIES`. If that
488
- configuration variable is not set, we default to True.
489
- If specified, use the input value of rebuild_registries, which should be a
490
- boolean value. If rebuild registries is true and get_plugin fails, rebuild
491
- the plugin registry, call then call get_plugin once more with
492
- rebuild_registries toggled off, so it only gets rebuilt once.
536
+ The name of the desired plugin.
537
+ rebuild_registries : bool (default=None)
538
+ Whether or not to rebuild the registries if get_plugin fails. If set to
539
+ None, default to the value of
540
+ `~/.config/pluginify/config.yaml:REBUILD_REGISTRIES`. If that
541
+ configuration variable is not set, we default to True.
542
+ If specified, use the input value of rebuild_registries, which should be a
543
+ boolean value. If rebuild registries is true and get_plugin fails, rebuild
544
+ the plugin registry, call then call get_plugin once more with
545
+ rebuild_registries toggled off, so it only gets rebuilt once.
493
546
 
494
547
  Returns
495
548
  -------
496
- An object of type ``<interface>Plugin`` where ``<interface>`` is the name of
497
- this interface.
549
+ <interface>Plugin
550
+ An object of type ``<interface>Plugin`` where ``<interface>`` is the name
551
+ of this interface.
498
552
 
499
553
  Raises
500
554
  ------
501
555
  PluginError
502
- If the specified plugin isn't found within the interface.
556
+ If the specified plugin isn't found within the interface.
503
557
  """
504
558
  return self.plugin_registry.get_class_plugin(self, name, rebuild_registries)
505
559
 
506
- def get_plugins(self):
560
+ def get_plugins(self) -> list:
507
561
  """Retrieve all module plugins for this interface."""
508
562
  return self.plugin_registry.get_class_plugins(self)
509
563
 
510
- def plugin_is_valid(self, plugin):
564
+ def plugin_is_valid(self, plugin) -> bool:
511
565
  """Check that an interface is valid.
512
566
 
513
567
  Check that the requested interface function has the correct call signature.
@@ -523,14 +577,17 @@ class BaseClassInterface(BaseInterface):
523
577
  Parameters
524
578
  ----------
525
579
  plugin : PluginObject
526
- A plugin object coming from _plugin_module_to_obj that needs to be validated.
580
+ A plugin object coming from _plugin_module_to_obj that needs to be
581
+ validated.
527
582
 
528
583
  Returns
529
584
  -------
530
585
  bool
531
- True if valid, False if invalid
586
+ True if valid, False if invalid
532
587
  """
533
- if plugin.family not in self.required_args:
588
+ family_required = not getattr(plugin, "data_tree", False)
589
+
590
+ if family_required and plugin.family not in self.required_args:
534
591
  raise PluginError(
535
592
  f"'{plugin.family}' must be added to required args list"
536
593
  f"\nfor '{self.name}' interface,"
@@ -538,7 +595,7 @@ class BaseClassInterface(BaseInterface):
538
595
  f"\nin '{plugin.module_name}' module"
539
596
  f"\nat '{plugin.module_path}'\n"
540
597
  )
541
- if plugin.family not in self.required_kwargs:
598
+ if family_required and plugin.family not in self.required_kwargs:
542
599
  raise PluginError(
543
600
  f"'{plugin.family}' must be added to required kwargs list"
544
601
  f"\nfor '{self.name}' interface,"
@@ -546,8 +603,14 @@ class BaseClassInterface(BaseInterface):
546
603
  f"\nin '{plugin.module_name}' module"
547
604
  f"\nat '{plugin.module_path}'\n"
548
605
  )
549
- expected_args = self.required_args[plugin.family]
550
- expected_kwargs = self.required_kwargs[plugin.family]
606
+
607
+ # NOTE: Likely will need to modify this; we need to discuss how we want to
608
+ # require specific sets of arguments for data_tree based plugins that do not
609
+ # require a family attribute
610
+ family_name = plugin.family if family_required else "data_tree"
611
+
612
+ expected_args = self.required_args[family_name]
613
+ expected_kwargs = self.required_kwargs[family_name]
551
614
 
552
615
  sig = inspect.signature(plugin.call)
553
616
  arg_list = []
@@ -596,11 +659,12 @@ class BaseClassInterface(BaseInterface):
596
659
 
597
660
  return True
598
661
 
599
- def plugins_all_valid(self):
662
+ def plugins_all_valid(self) -> bool:
600
663
  """Test the current interface by validating every Plugin.
601
664
 
602
665
  Returns
603
666
  -------
667
+ bool
604
668
  True if all plugins are valid, False if any plugin is invalid.
605
669
  """
606
670
  plugins = self.get_plugins()
@@ -609,7 +673,7 @@ class BaseClassInterface(BaseInterface):
609
673
  return False
610
674
  return True
611
675
 
612
- def test_interface(self):
676
+ def test_interface(self) -> dict:
613
677
  """Test the current interface by validating each Plugin and testing each method.
614
678
 
615
679
  Test this interface by opening every Plugin available to the interface,
@@ -623,6 +687,7 @@ class BaseClassInterface(BaseInterface):
623
687
 
624
688
  Returns
625
689
  -------
690
+ dict
626
691
  A dictionary containing three keys:
627
692
  'by_family', 'validity_check', 'func', and 'family'. The value for each
628
693
  of these keys is a dictionary whose keys are the names of the Plugins.
@@ -640,14 +705,17 @@ class BaseClassInterface(BaseInterface):
640
705
  plugins = self.get_plugins()
641
706
  all_valid = self.plugins_all_valid()
642
707
  family_list = []
643
- plugin_ids = {}
708
+ plugin_ids: dict = {}
644
709
  for plugin in plugins:
645
- if plugin.family not in family_list:
646
- family_list.append(plugin.family)
647
- plugin_ids[plugin.family] = []
648
- plugin_ids[plugin.family].append(plugin.id)
710
+ family_name = (
711
+ plugin.family if not getattr(plugin, "data_tree") else "data_tree"
712
+ )
713
+ if family_name not in family_list:
714
+ family_list.append(family_name)
715
+ plugin_ids[family_name] = []
716
+ plugin_ids[family_name].append(plugin.id)
649
717
 
650
- output = {
718
+ output: dict[str, Any] = {
651
719
  "all_valid": all_valid,
652
720
  "by_family": plugin_ids,
653
721
  "validity_check": {},
@@ -12,7 +12,10 @@ from pluginify.interfaces.base import BaseClassInterface
12
12
 
13
13
 
14
14
  class BaseDataModifierPlugin(BaseClassPlugin, abstract=True):
15
- """Dummy base class for data_modifier plugins."""
15
+ """Dummy base class for data_modifier plugins.
16
+
17
+ This base class is strictly used for testing pluginify.
18
+ """
16
19
 
17
20
  pass
18
21