pluginify 0.0.0.post1.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.
@@ -0,0 +1,38 @@
1
+ # # # This source code is subject to the license referenced at
2
+ # # # https://github.com/NRLMMD-GEOIPS.
3
+
4
+ """Data modifiers interface class.
5
+
6
+ This is a 'dummy' interface which is strictly used for testing pluginify. All plugins
7
+ of this interface are also 'dummy' plugins strictly used for testing this package.
8
+ """
9
+
10
+ from pluginify.interfaces.class_based_plugin import BaseClassPlugin
11
+ from pluginify.interfaces.base import BaseClassInterface
12
+
13
+
14
+ class BaseDataModifierPlugin(BaseClassPlugin, abstract=True):
15
+ """Dummy base class for data_modifier plugins."""
16
+
17
+ pass
18
+
19
+
20
+ class DataModifiersInterface(BaseClassInterface):
21
+ """Data Modifiers interface class.
22
+
23
+ This is a 'dummy' interface which is strictly used for testing pluginify. All
24
+ plugins of this interface are also 'dummy' plugins strictly used for testing this
25
+ package.
26
+ """
27
+
28
+ name = "data_modifiers"
29
+ plugin_class = BaseDataModifierPlugin
30
+
31
+ required_args = {
32
+ "standard": ["data", "config"],
33
+ }
34
+
35
+ required_kwargs = {"standard": []}
36
+
37
+
38
+ data_modifiers = DataModifiersInterface()
@@ -0,0 +1,210 @@
1
+ """Implements a base class for class-based plugins.
2
+
3
+ The base class implemented here would expose the call signature of the child plugin
4
+ class as `__call__()` while also providing hooks for pre- and post-processing.
5
+
6
+ The hooks are available as `_pre_call()` and `_post_call()`. They should be used to
7
+ implement common functionality that all plugins of this type should posess but which we
8
+ don't want developers to need to implement. They are intended to be overridden by the
9
+ child plugin-type class (e.g. BaseReaderPlugin). They should define what kwargs they
10
+ accept when defined on the plugin-type class but should accept their arguments from
11
+ `**kwargs` from `__call__()`.
12
+
13
+ The `call()` method should be overridden on the actual plugin class. It should provide
14
+ the data processing for the plugin. `__call__()`'s signature will be identical to that
15
+ of `call()` except that `call()` should not accept `**kwargs`. That should be consumed
16
+ by the hooks.
17
+
18
+ `__call__()` should not be overridden anywhere.
19
+ """
20
+
21
+ from __future__ import annotations
22
+ from abc import ABC, abstractmethod
23
+ import functools
24
+ import inspect
25
+
26
+
27
+ def valid_str_attr(cls, attr_name: str):
28
+ """Check that the given attribute is a non-empty string."""
29
+ attr_value = getattr(cls, attr_name, None)
30
+ if not isinstance(attr_value, str):
31
+ raise TypeError(f"{cls.__name__}.{attr_name} must be a string")
32
+ if not attr_value:
33
+ raise ValueError(f"{cls.__name__}.{attr_name} cannot be empty")
34
+
35
+
36
+ class BaseClassPlugin(ABC):
37
+ """The base class for class-based plugins.
38
+
39
+ All plugins are required to carry the following class attributes:
40
+
41
+ - interface: The interface type the plugin belongs to
42
+ (e.g. 'configs', 'data_modifiers').
43
+ This is typically provided by the interface-level plugin class and not the
44
+ individual plugin class.
45
+ - family: The family name of the plugin. This should be defined by the plugin class.
46
+ - name: The specific name of the plugin. This should be defined by the plugin class.
47
+
48
+ Subclasses of this base class must also implement the following methods:
49
+
50
+ - call(): The main method that performs the plugin's functionality. This method
51
+ should be implemented by the plugin class.
52
+ - _pre_call(): A hook method that can be overridden to preprocess data before
53
+ calling the main call() method. This method should accept the same arguments as
54
+ call() via `*args` and `**kwargs` and should, typically, be implemented by the
55
+ interface-level plugin class.
56
+ - _post_call(): A hook method that can be overridden to post-process data after
57
+ calling the main call() method. This method should accept the same arguments as
58
+ call() via `*args` and `**kwargs` and should, typically, be implemented by the
59
+ interface-level plugin class.
60
+
61
+ The purpose of `_pre_call()` and `_post_call()` is to allow for common
62
+ functionality that all plugins of a certain type should possess, without requiring
63
+ developers to implement this functionality in every plugin class. Initially, this
64
+ will be used to convert inputs from DataTree to other formats and back to DataTree
65
+ after processing, but it could be used for other common tasks as well.
66
+ """
67
+
68
+ # If set to True, we are in OBP. False means we are in a legacy procflow.
69
+ # Set up logic in this or interface classes to convert from DT to data type
70
+ # referenced in family
71
+
72
+ # If no family is provided, just assume it works with DT
73
+ data_tree = False
74
+ required_attributes = ["interface", "family", "name"]
75
+
76
+ def _check_interface_attribute(cls):
77
+ """Check the validity of the 'interface' attribute.
78
+
79
+ Checks to be sure that the interface attribute is a non-empty string and that it
80
+ is a known plugin interface.
81
+ """
82
+ valid_str_attr(cls, "interface")
83
+
84
+ def _check_family_attribute(cls):
85
+ """Check the validity of the 'family' attribute."""
86
+ valid_str_attr(cls, "family")
87
+
88
+ def _check_name_attribute(cls):
89
+ """Check the validity of the 'name' attribute."""
90
+ valid_str_attr(cls, "name")
91
+
92
+ @abstractmethod
93
+ def call(self, *args, **kwargs):
94
+ """Callable method to be implemented by the plugin class."""
95
+ pass
96
+
97
+ # hooks are intentionally loose; document their accepted kwargs
98
+ def _pre_call(self, data=None, *args, **kwargs):
99
+ """Preprocess the data before calling the main plugin method.
100
+
101
+ Parameters
102
+ ----------
103
+ data : The input data for the plugin.
104
+
105
+ Returns
106
+ -------
107
+ The processed data.
108
+ """
109
+ return data
110
+
111
+ def _post_call(self, data=None, *args, **kwargs):
112
+ """Post-process the data after calling the main plugin method.
113
+
114
+ Parameters
115
+ ----------
116
+ data : R
117
+ The output data from the plugin.
118
+
119
+ Returns
120
+ -------
121
+ The processed data.
122
+ """
123
+ return data
124
+
125
+ def _invoke(self, data=None, *args, **kwargs):
126
+ if data is None:
127
+ data = self.call(*args, **kwargs)
128
+ else:
129
+ data = self._pre_call(data, *args, **kwargs)
130
+ data = self.call(data, *args, **kwargs)
131
+ data = self._post_call(data, *args, **kwargs)
132
+ return data
133
+
134
+ def __init__(self, module=None):
135
+ """
136
+ Initialize the plugin object inheriting from BaseClasePlugin.
137
+
138
+ Parameters
139
+ ----------
140
+ module: ModuleType, default=None
141
+ The module from which the class-based plugin originated. This is used to
142
+ collect metadata from the module and attach it to the plugin object. The
143
+ metadata can then be used during validation to indicate where failing
144
+ plugins originated. If None, the 'testing' attributes will be set to a
145
+ string value that can also be used in tests.
146
+ """
147
+ if module:
148
+ self.module_name = module.__name__
149
+ self.module_path = module.__file__
150
+ else:
151
+ self.module_name = "Unknown."
152
+ self.module_path = "Unknown."
153
+
154
+ def __init_subclass__(cls, *, abstract=False, **kwargs) -> None:
155
+ """
156
+ Initialize a subclass of the plugin.
157
+
158
+ Parameters
159
+ ----------
160
+ abstract : bool, optional
161
+ If True, the subclass is treated as abstract. When treated as abstract,
162
+ validation is disabled in __init_subclass__. Defaults to False.
163
+
164
+ Raises
165
+ ------
166
+ TypeError
167
+ If the subclass does not define the required attributes.
168
+ TypeError
169
+ If the subclass does not implement the call() method.
170
+ TypeError
171
+ If the subclass overrides the __call__() method.
172
+
173
+ Returns
174
+ -------
175
+ None
176
+ """
177
+ super().__init_subclass__(**kwargs)
178
+
179
+ # Treat the root as abstract and honor explicit marker
180
+ if cls is BaseClassPlugin or abstract:
181
+ cls.__plugin_abstract__ = True
182
+ return
183
+
184
+ # Enforce required attributes and run attribute checkers if they exist
185
+ for attr in cls.required_attributes:
186
+ # Ensure required attributes are defined
187
+ if not hasattr(cls, attr):
188
+ raise TypeError(f"{cls.__name__} must define '{attr}' attribute")
189
+
190
+ # Run attribute checker for the current attribute if it exists
191
+ attribute_checker = getattr(cls, f"_check_{attr}_attribute", None)
192
+ if attribute_checker is not None:
193
+ attribute_checker(cls)
194
+
195
+ # Prevent overriding __call__ in a True class-based plugin
196
+ if "__call__" in cls.__dict__:
197
+ raise TypeError(f"{cls.__name__} cannot override __call__")
198
+
199
+ try:
200
+ call_method = cls.__dict__.get("call")
201
+ except AttributeError:
202
+ raise TypeError(f"{cls.__name__} must implement call()")
203
+
204
+ @functools.wraps(call_method)
205
+ def _call(self, data=None, *args, **kwargs):
206
+ return cls._invoke(self, data, *args, **kwargs)
207
+
208
+ _call.__signature__ = inspect.signature(call_method) # mirror only call()
209
+ _call.__annotations__ = getattr(call_method, "__annotations__", {})
210
+ cls.__call__ = _call
@@ -0,0 +1,27 @@
1
+ # # # This source code is subject to the license referenced at
2
+ # # # https://github.com/NRLMMD-GEOIPS.
3
+
4
+ """Configs interface class.
5
+
6
+ This is a 'dummy' interface which is strictly used for testing pluginify. All plugins of
7
+ this interface are also 'dummy' plugins strictly used for testing this package.
8
+ """
9
+
10
+ from pluginify.interfaces.base import BaseYamlInterface
11
+ from pluginify.pydantic_models.v1.configs import ConfigPluginModel
12
+
13
+
14
+ class ConfigsInterface(BaseYamlInterface):
15
+ """Configs interface class.
16
+
17
+ This is a 'dummy' interface which is strictly used for testing pluginify. All
18
+ plugins of this interface are also 'dummy' plugins strictly used for testing this
19
+ package.
20
+ """
21
+
22
+ name = "configs"
23
+ use_pydantic = True
24
+ validator = ConfigPluginModel
25
+
26
+
27
+ configs = ConfigsInterface()