pluginify 1.0.0.post16.dev0__py3-none-any.whl → 1.0.0.post48.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.
@@ -6,6 +6,8 @@
6
6
  This is a 'dummy' plugin which is strictly used for testing pluginify.
7
7
  """
8
8
 
9
+ from typing import Any
10
+
9
11
  from pluginify.interfaces.class_based.data_modifiers import BaseDataModifierPlugin
10
12
 
11
13
 
@@ -19,21 +21,21 @@ class CuboidDataModifierPlugin(BaseDataModifierPlugin):
19
21
  interface = "data_modifiers"
20
22
  family = "standard"
21
23
 
22
- def call(self, data, config):
24
+ def call(self, data: Any, config: dict) -> Any:
23
25
  """Modify the cuboid-like dataset using parameters from 'config'.
24
26
 
25
27
  Parameters
26
28
  ----------
27
- data: xarray.Dataset
28
- - The incoming xarray.Dataset to modify.
29
- config: dict
30
- - A dictionary representing the configuration plugin used to modify this
31
- dataset.
29
+ data : Any
30
+ The incoming dataset to modify.
31
+ config : dict
32
+ A dictionary representing the configuration plugin used to modify this
33
+ dataset.
32
34
 
33
35
  Returns
34
36
  -------
35
- xarray.Dataset
36
- - The modified dataset based on the parameters set in 'config'.
37
+ Any
38
+ The modified dataset based on the parameters set in 'config'.
37
39
  """
38
40
  for md_key, md_val in config["spec"].items():
39
41
  data[md_key] = md_val
@@ -4,8 +4,14 @@
4
4
  """
5
5
  Pydantic model for pluginify configuration plugins.
6
6
 
7
- NOTE: these plugins are not intended to be used. They are just provided as an example to
8
- see how the plugin registry functions and for testing this package.
7
+ These models define the schema for YAML-based configuration plugins at API version
8
+ ``pluginify/v1``. The ``ConfigPluginModel`` is the top-level model that
9
+ ``PluginRegistry.load_plugin()`` dispatches to when ``apiVersion`` is
10
+ ``pluginify/v1`` and ``interface`` is ``configs``.
11
+
12
+ NOTE: these plugins are not intended for production. They are provided as a reference
13
+ implementation to demonstrate how the Pydantic validation and plugin registry systems
14
+ work, and to support unit tests for this package.
9
15
  """
10
16
 
11
17
  from typing import Literal, Optional, Union
@@ -1,30 +1,49 @@
1
1
  # # # This source code is subject to the license referenced at
2
2
  # # # https://github.com/NRLMMD-GEOIPS.
3
3
 
4
- """Pluginify utility module."""
4
+ """Utility functions for pluginify.
5
+
6
+ Provides generic helpers used throughout the package:
7
+
8
+ - ``merge_nested_dicts`` — recursively merge two dictionaries while
9
+ preserving existing values in the destination.
10
+
11
+ See Also
12
+ --------
13
+ pluginify.utils.validators : Plugin registry validation helpers.
14
+ pluginify.utils.context_managers : Optional import context manager.
15
+ """
5
16
 
6
17
  from copy import deepcopy
7
18
 
8
19
 
9
- def merge_nested_dicts(dest, src, in_place=True, replace=False):
10
- """Perform an in-place merge of src into dest.
20
+ def merge_nested_dicts(
21
+ dest: dict, src: dict, in_place: bool = True, replace: bool = False
22
+ ) -> dict | None:
23
+ """Merge ``src`` into ``dest`` recursively.
11
24
 
12
- Performs an in-place merge of src into dest while preserving any values that already
13
- exist in dest.
25
+ Performs an in-place merge of ``src`` into ``dest`` while preserving any values
26
+ that already exist in ``dest``. If ``replace`` is ``True``, matching keys in
27
+ ``dest`` are overwritten by ``src`` instead.
14
28
 
15
29
  Parameters
16
30
  ----------
17
- dest: dict
18
- - The destination dictionary to merge to
19
- src: dict
20
- - The source dictionary to merge into dest
21
- in_place: bool, default=True
22
- - Whether or not to merge directly into dest or to create a deepcopy of dest
23
- and return that as the final result. By default, this function will merge
24
- directly into dest.
25
- replace: boo, default=False
26
- - Whether or not to override duplicate keys in src and dest with the contents of
27
- src. By default, replace is set to false and overriding will not occur.
31
+ dest : dict
32
+ The destination dictionary to merge to.
33
+ src : dict
34
+ The source dictionary to merge into ``dest``.
35
+ in_place : bool, optional
36
+ Whether to merge directly into ``dest`` (``True``) or to create a deepcopy
37
+ of ``dest`` and return that as the result (``False``). Defaults to ``True``.
38
+ replace : bool, optional
39
+ Whether to override duplicate keys in ``dest`` with the contents of
40
+ ``src``. Defaults to ``False``.
41
+
42
+ Returns
43
+ -------
44
+ dict or None
45
+ The merged dict when ``in_place`` is ``False``, or ``None`` when
46
+ ``in_place`` is ``True``.
28
47
  """
29
48
  if not in_place:
30
49
  final_dest = deepcopy(dest)
@@ -49,7 +68,7 @@ def merge_nested_dicts(dest, src, in_place=True, replace=False):
49
68
  try:
50
69
  final_dest.update(src | final_dest)
51
70
  except (AttributeError, TypeError):
52
- return
71
+ return None
53
72
  try:
54
73
  for key, val in final_dest.items(): # NOQA
55
74
  try:
@@ -60,3 +79,4 @@ def merge_nested_dicts(dest, src, in_place=True, replace=False):
60
79
  raise
61
80
  if not in_place:
62
81
  return final_dest
82
+ return None
@@ -1,24 +1,37 @@
1
1
  # # # This source code is subject to the license referenced at
2
2
  # # # https://github.com/NRLMMD-GEOIPS.
3
3
 
4
- """Module for handling optional dependencies throughout pluginify."""
4
+ """Context managers for handling optional dependencies throughout pluginify."""
5
5
 
6
6
  import logging
7
7
  from contextlib import contextmanager
8
8
  import traceback
9
+ from typing import Generator
9
10
 
10
11
  LOG = logging.getLogger(__name__)
11
12
 
12
13
 
13
14
  @contextmanager
14
- def import_optional_dependencies(loglevel="info"):
15
+ def import_optional_dependencies(loglevel: str = "info") -> Generator[None, None, None]:
15
16
  """Attempt to import a package and log the event if the import fails.
16
17
 
18
+ Use as a context manager wrapping potentially-failing imports:
19
+
20
+ .. code-block:: python
21
+
22
+ with import_optional_dependencies():
23
+ import some_optional_package
24
+
17
25
  Parameters
18
26
  ----------
19
- loglevel : str
20
- Name of the log level to write to. May be any valid log level (e.g. debug, info,
21
- etc.).
27
+ loglevel : str, optional
28
+ Name of the log level to write to. May be any valid log level (e.g.
29
+ ``"debug"``, ``"info"``, etc.). Defaults to ``"info"``.
30
+
31
+ Yields
32
+ ------
33
+ None
34
+ Does not yield a value; the wrapped block executes in-place.
22
35
  """
23
36
  try:
24
37
  yield None
@@ -29,4 +42,3 @@ def import_optional_dependencies(loglevel="info"):
29
42
  err_str += "If you need it, install it."
30
43
 
31
44
  getattr(LOG, loglevel)(err_str)
32
- # print(err_str)
@@ -1,9 +1,11 @@
1
1
  # # # This source code is subject to the license referenced at
2
2
  # # # https://github.com/NRLMMD-GEOIPS.
3
3
 
4
- """Pluginify validators module.
4
+ """Unit-testing support for validating plugin registry contents.
5
5
 
6
- Currently only implements a PluginRegistryValidator class.
6
+ Provides the ``PluginRegistryValidator`` class, a subclass of ``PluginRegistry``
7
+ that adds methods to verify the structural correctness of registry files and the
8
+ plugins they contain.
7
9
  """
8
10
 
9
11
  from importlib import metadata, import_module
@@ -17,15 +19,42 @@ from pluginify.plugin_registry import PluginRegistry
17
19
 
18
20
 
19
21
  class PluginRegistryValidator(PluginRegistry):
20
- """Subclass of PluginRegistry which adds functionality for unit testing."""
22
+ """Subclass of ``PluginRegistry`` which adds functionality for unit testing.
21
23
 
22
- def __init__(self, namespace, fpaths=None):
23
- """Initialize TestPluginRegistry Class."""
24
+ Provides methods to validate that registry files have the correct top-level
25
+ structure, that all required plugin attributes are present, and that
26
+ registered interfaces reference known interface names.
27
+ """
28
+
29
+ def __init__(self, namespace: str, fpaths: list[str] | None = None) -> None:
30
+ """Initialize a ``PluginRegistryValidator``.
31
+
32
+ Parameters
33
+ ----------
34
+ namespace : str
35
+ The plugin-package namespace to operate on.
36
+ fpaths : list of str, optional
37
+ Explicit list of registry file paths to use. If ``None``, registry
38
+ files are located automatically from the namespace.
39
+ """
24
40
  self.namespace = namespace
25
41
  super().__init__(self.namespace, _test_registry_files=fpaths)
26
42
 
27
- def validate_plugin_types_exist(self, reg_dict, reg_path):
28
- """Test that all top level plugin types exist in each registry file."""
43
+ def validate_plugin_types_exist(self, reg_dict: dict, reg_path: str) -> None:
44
+ """Test that all top-level plugin types exist in each registry file.
45
+
46
+ Parameters
47
+ ----------
48
+ reg_dict : dict
49
+ The contents of a single registry file.
50
+ reg_path : str
51
+ The path to the registry file being validated.
52
+
53
+ Raises
54
+ ------
55
+ PluginRegistryError
56
+ If an expected plugin type is missing from the registry.
57
+ """
29
58
  expected_plugin_types = ["yaml_based", "class_based", "text_based"]
30
59
  for p_type in expected_plugin_types:
31
60
  if p_type not in reg_dict:
@@ -33,7 +62,7 @@ class PluginRegistryValidator(PluginRegistry):
33
62
  error_str += f" wasn't. This was in file '{reg_path}'."
34
63
  raise PluginRegistryError(error_str)
35
64
 
36
- def validate_all_registries(self):
65
+ def validate_all_registries(self) -> None:
37
66
  """Validate all registries in the current installation.
38
67
 
39
68
  This should be run during testing, but not at runtime.
@@ -44,8 +73,16 @@ class PluginRegistryValidator(PluginRegistry):
44
73
  pkg_plugins = json.load(open(reg_path, "r"))
45
74
  self.validate_registry(pkg_plugins, reg_path)
46
75
 
47
- def validate_registry(self, current_registry, fpath):
48
- """Test all plugins found in registered plugins for their validity."""
76
+ def validate_registry(self, current_registry: dict, fpath: str) -> None:
77
+ """Test all plugins found in registered plugins for their validity.
78
+
79
+ Parameters
80
+ ----------
81
+ current_registry : dict
82
+ The contents of a single registry file.
83
+ fpath : str
84
+ The path to the registry file being validated.
85
+ """
49
86
  try:
50
87
  self.validate_plugin_types_exist(current_registry, fpath)
51
88
  except PluginRegistryError as e:
@@ -93,8 +130,32 @@ class PluginRegistryValidator(PluginRegistry):
93
130
  else:
94
131
  raise PluginRegistryError(e)
95
132
 
96
- def validate_plugin_attrs(self, plugin_type, interface, name, plugin):
97
- """Test non-product plugin for all required attributes."""
133
+ def validate_plugin_attrs(
134
+ self,
135
+ plugin_type: str,
136
+ interface: str,
137
+ name: str | tuple[str, str],
138
+ plugin: dict,
139
+ ) -> None:
140
+ """Test a plugin for all required attributes.
141
+
142
+ Parameters
143
+ ----------
144
+ plugin_type : str
145
+ The plugin type (``"yaml_based"``, ``"class_based"``).
146
+ interface : str
147
+ The interface name (e.g. ``"configs"``, ``"products"``).
148
+ name : str or tuple of str
149
+ The plugin name. For product plugins this is a ``(source, name)``
150
+ tuple.
151
+ plugin : dict
152
+ The plugin metadata dictionary from the registry.
153
+
154
+ Raises
155
+ ------
156
+ PluginRegistryError
157
+ If the plugin is missing one or more required attributes.
158
+ """
98
159
  missing = []
99
160
  if plugin_type == "yaml_based" and interface != "products":
100
161
  attrs = [
@@ -137,8 +198,20 @@ class PluginRegistryValidator(PluginRegistry):
137
198
  f"top-level properties: '{missing}'"
138
199
  )
139
200
 
140
- def validate_registry_interfaces(self, current_registry):
141
- """Test Plugin Registry interfaces validity."""
201
+ def validate_registry_interfaces(self, current_registry: dict) -> None:
202
+ """Validate that registry interfaces reference known interface names.
203
+
204
+ Parameters
205
+ ----------
206
+ current_registry : dict
207
+ The contents of a single registry file.
208
+
209
+ Raises
210
+ ------
211
+ PluginRegistryError
212
+ If one or more interfaces in the registry do not match a known
213
+ interface name from any installed package's ``interfaces/__init__.py``.
214
+ """
142
215
  yaml_interfaces = []
143
216
  class_interfaces = []
144
217
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pluginify
3
- Version: 1.0.0.post16.dev0
3
+ Version: 1.0.0.post48.dev0
4
4
  Summary: Pluginify package
5
5
  License: LICENSE
6
6
  License-File: LICENSE
@@ -14,12 +14,14 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: Programming Language :: Python :: 3.14
15
15
  Classifier: Programming Language :: Python :: 3.15
16
16
  Provides-Extra: doc
17
+ Requires-Dist: brassy (>=0.4.1) ; extra == "doc"
17
18
  Requires-Dist: docstring_parser
18
19
  Requires-Dist: jsonschema (>4.18.0)
19
20
  Requires-Dist: lexeme-type
20
21
  Requires-Dist: platformdirs
21
22
  Requires-Dist: pydantic (>=2.10.0)
22
23
  Requires-Dist: pyyaml
24
+ Requires-Dist: rich ; extra == "doc"
23
25
  Requires-Dist: sphinxcontrib-typer ; extra == "doc"
24
26
  Requires-Dist: typer
25
27
  Project-URL: Repository, https://github.com/NRLMMD-GEOIPS/pluginify
@@ -0,0 +1,23 @@
1
+ pluginify/__init__.py,sha256=kA6eydjcn5rZXGaRLNbPaNv9xSE_7j4LFm_fd7KPKK4,1019
2
+ pluginify/_version.py,sha256=H9eXYFYOjedMKbny5RUj8E7M1NurC6fZ_9_TCskJa5o,239
3
+ pluginify/commandline_typer.py,sha256=8HhuDL2lfd-fSRWDfCAkODm9r9vToXYJ8HA94Fi6HRo,11514
4
+ pluginify/config.py,sha256=9HlYVvUD5LsbPm3uCddfjiM93a6HnWNc3ckcvciBuog,5303
5
+ pluginify/create_plugin_registries.py,sha256=CE7s-oblRmx9I98OPVNjwzo82nXgT0ANoZOGRJ919Go,52349
6
+ pluginify/errors.py,sha256=xOvJa9fvg1txxxnB9woBFWcGYYfQM7LmMGlkg013lZs,687
7
+ pluginify/interfaces/__init__.py,sha256=WvZcABXn9abFARK276ObZANmMaM0o22TIz90sesKHa0,873
8
+ pluginify/interfaces/base.py,sha256=Oy7Vbp6WlB4Pklhfk1ZQgioVlFu3VR8nP0R0r0z84Aw,29041
9
+ pluginify/interfaces/class_based/data_modifiers.py,sha256=euPMP8cuEIyUaepn7JAmlqP3CN4W2UFrgo4EFLivo3I,1160
10
+ pluginify/interfaces/class_based_plugin.py,sha256=cyGDNmzCqBd_csY9Z-oyI4wKma7TrBnbN8udlvesubA,11656
11
+ pluginify/interfaces/yaml_based/configs.py,sha256=QSXQQd14-iStJVPwklE6Sc1HTHFJ2g3SEhapbP9AHss,879
12
+ pluginify/plugin_registry.py,sha256=X-hETQD7W7jjNJaTWx8IKKEiTIUSyMMgoE5iVhENruQ,44269
13
+ pluginify/plugins/classes/data_modifiers/cuboid.py,sha256=nVBwc_xPCLMcfeIkdmwmZF9zByZWv-Bi6XFw_X7MLAM,1218
14
+ pluginify/plugins/yaml/configs/stucco.yaml,sha256=OXLWzmmzxSAWs-MzSlmEa-OLGJzVJzrVQZwgGPL2jqE,376
15
+ pluginify/pydantic_models/v1/configs.py,sha256=SAA9hJOrw0qaUJppV5JQ16FpiWIPxGUMg6pPpUhKLes,2568
16
+ pluginify/utils/__init__.py,sha256=tbEpiNRhkg3c9bUeRMpR-T977QYbeSVpBm6yGRb96Ik,2657
17
+ pluginify/utils/context_managers.py,sha256=E75ziUTBhGz47Q4Dcikkvl1oPZhj2hwYCQK5KGuRD-U,1308
18
+ pluginify/utils/validators.py,sha256=-s1np0qd0D9cjzLU1TkX8Hxf68UhKypux9T5Mk83Xoc,10052
19
+ pluginify-1.0.0.post48.dev0.dist-info/METADATA,sha256=SbeYGYsj43G4ucn0qfB-623nfWz7TkVbwangHYE2tvo,2265
20
+ pluginify-1.0.0.post48.dev0.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
21
+ pluginify-1.0.0.post48.dev0.dist-info/entry_points.txt,sha256=kCm7HOEEzSnPyDpy98W-LYYmlLYyTnRqOj40UqxX6-w,111
22
+ pluginify-1.0.0.post48.dev0.dist-info/licenses/LICENSE,sha256=sIBqTiU78gw5bC9MZMySNxzCE7DIlOcDqUWdmflkDsY,7266
23
+ pluginify-1.0.0.post48.dev0.dist-info/RECORD,,
@@ -1,23 +0,0 @@
1
- pluginify/__init__.py,sha256=NvdicJtfy-dZZ8tWjR74lDBHGV6rlaH8pAJ7UHyTTog,391
2
- pluginify/_version.py,sha256=imnhJ-_dm5JxhJEQNyiL-oXxluDKdxh2Iv533XHd1Ok,239
3
- pluginify/commandline_typer.py,sha256=i0aVQEABfS8oL6ATUmzMqgtQQLhw0nMQmYzwpPzTF8k,11361
4
- pluginify/config.py,sha256=ffmzSyAjPNePkSZrqinu20ZJkbmG-L4TREgRfFmPL4M,4997
5
- pluginify/create_plugin_registries.py,sha256=LokHQRfNPKeIYIAFaNSNhNZWxy1_fX5-N7YJ5BD4U5k,50382
6
- pluginify/errors.py,sha256=kkdipLfxvj8f-Pp53SD3RPnD9QS3samBLlQBjBskZP8,373
7
- pluginify/interfaces/__init__.py,sha256=OkhHHE8WUEh12n3VrAWfMUv2-AAz7wfy5OcEyY-KtTc,461
8
- pluginify/interfaces/base.py,sha256=CMgPG2NpodrECXXJbayTIVt8Vqrrxd_QADAgGM5qgUk,26749
9
- pluginify/interfaces/class_based/data_modifiers.py,sha256=INQCUaXWpUpRIljjhvg-ndUB_sRoMt7pzEVVEM3PeAQ,1094
10
- pluginify/interfaces/class_based_plugin.py,sha256=IA1bo33yior-udeaX_Cv8X8AA8syufI3rvxeMdv5hls,8431
11
- pluginify/interfaces/yaml_based/configs.py,sha256=3U_nKCm2mIVfA_cRl-VUmu8JrQQQ3u24eshH46a9GI0,808
12
- pluginify/plugin_registry.py,sha256=PB2k7g4lnONNkMPEebbgZTSH3hEYmo_9wcSvlS5aUAI,43416
13
- pluginify/plugins/classes/data_modifiers/cuboid.py,sha256=1pRE04bL5L5MHRHnky_hvKx2W-OkhzRLxgBPbbfMvMo,1211
14
- pluginify/plugins/yaml/configs/stucco.yaml,sha256=OXLWzmmzxSAWs-MzSlmEa-OLGJzVJzrVQZwgGPL2jqE,376
15
- pluginify/pydantic_models/v1/configs.py,sha256=KfWsIveOqPOi_J53odFFY6gFOHNYPpwQdPDqRXCfpnI,2226
16
- pluginify/utils/__init__.py,sha256=gdy4E4yEI9lrHbWPeBn0Bspj7iz4z6r8BAjbmHf_hpM,2088
17
- pluginify/utils/context_managers.py,sha256=1-bREbijNNc5PrqMIihKqxlxNaK-YZ3hj5XGpHiYmfw,926
18
- pluginify/utils/validators.py,sha256=ssfcUsvUlYfhWt5AjncU66KD4qDSv0N1BdR99ytvQjY,7681
19
- pluginify-1.0.0.post16.dev0.dist-info/METADATA,sha256=KJc6Hg4VEUOYgjeTLS2RVoGObQn5Z5a9FYe8k9Z-gCU,2179
20
- pluginify-1.0.0.post16.dev0.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
21
- pluginify-1.0.0.post16.dev0.dist-info/entry_points.txt,sha256=kCm7HOEEzSnPyDpy98W-LYYmlLYyTnRqOj40UqxX6-w,111
22
- pluginify-1.0.0.post16.dev0.dist-info/licenses/LICENSE,sha256=sIBqTiU78gw5bC9MZMySNxzCE7DIlOcDqUWdmflkDsY,7266
23
- pluginify-1.0.0.post16.dev0.dist-info/RECORD,,