lockss-turtles 0.5.0.dev3__py3-none-any.whl → 0.6.0.dev1__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.
@@ -1,37 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- # Copyright (c) 2000-2023, Board of Trustees of Leland Stanford Jr. University
4
- #
5
- # Redistribution and use in source and binary forms, with or without
6
- # modification, are permitted provided that the following conditions are met:
7
- #
8
- # 1. Redistributions of source code must retain the above copyright notice,
9
- # this list of conditions and the following disclaimer.
10
- #
11
- # 2. Redistributions in binary form must reproduce the above copyright notice,
12
- # this list of conditions and the following disclaimer in the documentation
13
- # and/or other materials provided with the distribution.
14
- #
15
- # 3. Neither the name of the copyright holder nor the names of its contributors
16
- # may be used to endorse or promote products derived from this software without
17
- # specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
-
31
- __version__ = '0.5.0-dev3'
3
+ """
4
+ Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin
5
+ registries.
6
+ """
7
+
8
+ __version__ = '0.6.0-dev1'
32
9
 
33
10
  __copyright__ = '''
34
- Copyright (c) 2000-2023, Board of Trustees of Leland Stanford Jr. University
11
+ Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
35
12
  '''.strip()
36
13
 
37
14
  __license__ = __copyright__ + '\n\n' + '''
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- # Copyright (c) 2000-2023, Board of Trustees of Leland Stanford Jr. University
3
+ # Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -28,6 +28,6 @@
28
28
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
29
  # POSSIBILITY OF SUCH DAMAGE.
30
30
 
31
- import lockss.turtles.cli
31
+ from lockss.turtles.cli import main
32
32
 
33
- lockss.turtles.cli.main()
33
+ main()
lockss/turtles/app.py CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- # Copyright (c) 2000-2023, Board of Trustees of Leland Stanford Jr. University
3
+ # Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -28,103 +28,79 @@
28
28
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
29
  # POSSIBILITY OF SUCH DAMAGE.
30
30
 
31
- import importlib.resources
31
+ from collections.abc import Callable
32
+ import importlib.resources as IR
32
33
  from pathlib import Path
34
+ from typing import Dict, List, Optional, Tuple, Union
33
35
 
36
+ from lockss.pybasic.fileutil import path
34
37
  import xdg
35
38
 
36
- from lockss.turtles.plugin import Plugin
37
- from lockss.turtles.plugin_registry import PluginRegistry, PluginRegistryCatalog
38
- from lockss.turtles.plugin_set import PluginSet, PluginSetCatalog
39
- import lockss.turtles.resources
40
- from lockss.turtles.util import _load_and_validate, _path
39
+ from . import resources as __resources__
40
+ from .plugin import Plugin
41
+ from .plugin_registry import PluginRegistry, PluginRegistryCatalog
42
+ from .plugin_set import PluginSet, PluginSetCatalog
43
+ from .util import YamlT, load_and_validate
41
44
 
42
45
 
43
46
  class TurtlesApp(object):
44
47
 
45
- XDG_CONFIG_DIR = xdg.xdg_config_home().joinpath(__package__)
48
+ XDG_CONFIG_DIR: Path = xdg.xdg_config_home().joinpath(__package__)
46
49
 
47
- USR_CONFIG_DIR = Path('/usr/local/share', __package__)
50
+ USR_CONFIG_DIR: Path = Path('/usr/local/share', __package__)
48
51
 
49
- ETC_CONFIG_DIR = Path('/etc', __package__)
52
+ ETC_CONFIG_DIR: Path = Path('/etc', __package__)
50
53
 
51
- CONFIG_DIRS = [XDG_CONFIG_DIR, USR_CONFIG_DIR, ETC_CONFIG_DIR]
54
+ CONFIG_DIRS: List[Path] = [XDG_CONFIG_DIR, USR_CONFIG_DIR, ETC_CONFIG_DIR]
52
55
 
53
- PLUGIN_REGISTRY_CATALOG = 'plugin-registry-catalog.yaml'
56
+ PLUGIN_REGISTRY_CATALOG: str = 'plugin-registry-catalog.yaml'
54
57
 
55
- PLUGIN_SET_CATALOG = 'plugin-set-catalog.yaml'
58
+ PLUGIN_SET_CATALOG: str = 'plugin-set-catalog.yaml'
56
59
 
57
- PLUGIN_SIGNING_CREDENTIALS = 'plugin-signing-credentials.yaml'
60
+ PLUGIN_SIGNING_CREDENTIALS: str = 'plugin-signing-credentials.yaml'
58
61
 
59
- PLUGIN_SIGNING_CREDENTIALS_SCHEMA = 'plugin-signing-credentials-schema.json'
62
+ PLUGIN_SIGNING_CREDENTIALS_SCHEMA: str = 'plugin-signing-credentials-schema.json'
60
63
 
61
- @staticmethod
62
- def _default_files(file_str):
63
- return [dir_path.joinpath(file_str) for dir_path in TurtlesApp.CONFIG_DIRS]
64
-
65
- @staticmethod
66
- def _select_file(file_str, preselected=None):
67
- if preselected:
68
- preselected = _path(preselected)
69
- if not preselected.is_file():
70
- raise FileNotFoundError(str(preselected))
71
- return preselected
72
- choices = TurtlesApp._default_files(file_str)
73
- ret = next(filter(Path.is_file, choices), None)
74
- if ret is None:
75
- raise FileNotFoundError(' or '.join(map(str, choices)))
76
- return ret
77
-
78
- def __init__(self):
64
+ def __init__(self) -> None:
79
65
  super().__init__()
80
- self._password = None
81
- self._plugin_registries = None
82
- self._plugin_sets = None
83
- self._plugin_signing_credentials = None
66
+ self._password: Optional[Callable[[], str]] = None
67
+ self._plugin_registries: Optional[List[PluginRegistry]] = None
68
+ self._plugin_sets: Optional[List[PluginSet]] = None
69
+ self._plugin_signing_credentials: YamlT = None
84
70
 
85
- # Returns plugin_id -> (set_id, jar_path, plugin)
86
- def build_plugin(self, plugin_ids):
71
+ def build_plugin(self, plugin_ids: List[str]) -> Dict[str, Tuple[str, Path, Plugin]]:
87
72
  return {plugin_id: self._build_one_plugin(plugin_id) for plugin_id in plugin_ids}
88
73
 
89
- def default_plugin_registry_catalogs(self):
90
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_REGISTRY_CATALOG)
91
-
92
- def default_plugin_set_catalogs(self):
93
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_SET_CATALOG)
94
-
95
- def default_plugin_signing_credentials(self):
96
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
97
-
98
74
  # Returns (src_path, plugin_id) -> list of (registry_id, layer_id, dst_path, plugin)
99
- def deploy_plugin(self, src_paths, layer_ids, interactive=False):
75
+ def deploy_plugin(self, src_paths: List[Path], layer_ids: List[str], interactive: bool=False) -> Dict[Tuple[Path, str], List[Tuple[str, str, Optional[Path], Optional[Plugin]]]]:
100
76
  plugin_ids = [Plugin.id_from_jar(src_path) for src_path in src_paths]
101
77
  return {(src_path, plugin_id): self._deploy_one_plugin(src_path,
102
78
  plugin_id,
103
79
  layer_ids,
104
80
  interactive=interactive) for src_path, plugin_id in zip(src_paths, plugin_ids)}
105
81
 
106
- def load_plugin_registries(self, plugin_registry_catalog_path=None):
82
+ def load_plugin_registries(self, plugin_registry_catalog_path: Optional[Union[Path, str]]=None) -> None:
107
83
  if self._plugin_registries is None:
108
84
  plugin_registry_catalog = PluginRegistryCatalog.from_path(self.select_plugin_registry_catalog(plugin_registry_catalog_path))
109
85
  self._plugin_registries = list()
110
86
  for plugin_registry_file in plugin_registry_catalog.get_plugin_registry_files():
111
87
  self._plugin_registries.extend(PluginRegistry.from_path(plugin_registry_file))
112
88
 
113
- def load_plugin_sets(self, plugin_set_catalog_path=None):
89
+ def load_plugin_sets(self, plugin_set_catalog_path: Optional[Union[Path, str]]=None) -> None:
114
90
  if self._plugin_sets is None:
115
91
  plugin_set_catalog = PluginSetCatalog.from_path(self.select_plugin_set_catalog(plugin_set_catalog_path))
116
92
  self._plugin_sets = list()
117
93
  for plugin_set_file in plugin_set_catalog.get_plugin_set_files():
118
94
  self._plugin_sets.extend(PluginSet.from_path(plugin_set_file))
119
95
 
120
- def load_plugin_signing_credentials(self, plugin_signing_credentials_path=None):
96
+ def load_plugin_signing_credentials(self, plugin_signing_credentials_path: Optional[Union[Path, str]]=None) -> None:
121
97
  if self._plugin_signing_credentials is None:
122
- plugin_signing_credentials_path = _path(plugin_signing_credentials_path) if plugin_signing_credentials_path else self._select_file(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
123
- with importlib.resources.path(lockss.turtles.resources, TurtlesApp.PLUGIN_SIGNING_CREDENTIALS_SCHEMA) as plugin_signing_credentials_schema_path:
124
- self._plugin_signing_credentials = _load_and_validate(plugin_signing_credentials_schema_path, plugin_signing_credentials_path)
98
+ plugin_signing_credentials_path = path(plugin_signing_credentials_path) if plugin_signing_credentials_path else self._select_file(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
99
+ with IR.path(__resources__, TurtlesApp.PLUGIN_SIGNING_CREDENTIALS_SCHEMA) as plugin_signing_credentials_schema_path:
100
+ self._plugin_signing_credentials = load_and_validate(plugin_signing_credentials_schema_path, plugin_signing_credentials_path)
125
101
 
126
102
  # Returns plugin_id -> list of (registry_id, layer_id, dst_path, plugin)
127
- def release_plugin(self, plugin_ids, layer_ids, interactive=False):
103
+ def release_plugin(self, plugin_ids: List[str], layer_ids: List[str], interactive: bool=False) -> Dict[str, List[Tuple[str, str, Path, Plugin]]]:
128
104
  # ... plugin_id -> (set_id, jar_path, plugin)
129
105
  ret1 = self.build_plugin(plugin_ids)
130
106
  jar_paths = [jar_path for set_id, jar_path, plugin in ret1.values()]
@@ -134,54 +110,85 @@ class TurtlesApp(object):
134
110
  interactive=interactive)
135
111
  return {plugin_id: val for (jar_path, plugin_id), val in ret2.items()}
136
112
 
137
- def select_plugin_registry_catalog(self, preselected=None):
113
+ def select_plugin_registry_catalog(self, preselected: Optional[Union[Path, str]]=None) -> Path:
138
114
  return TurtlesApp._select_file(TurtlesApp.PLUGIN_REGISTRY_CATALOG, preselected)
139
115
 
140
- def select_plugin_set_catalog(self, preselected=None):
116
+ def select_plugin_set_catalog(self, preselected: Optional[Union[Path, str]]=None) -> Path:
141
117
  return TurtlesApp._select_file(TurtlesApp.PLUGIN_SET_CATALOG, preselected)
142
118
 
143
- def select_plugin_signing_credentials(self, preselected=None):
119
+ def select_plugin_signing_credentials(self, preselected: Optional[Union[Path, str]]=None) -> Path:
144
120
  return TurtlesApp._select_file(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS, preselected)
145
121
 
146
- def set_password(self, pw):
147
- self._password = pw if callable(pw) else lambda x: pw
122
+ def set_password(self, pw: Union[Callable[[], str], str]) -> None:
123
+ self._password = pw if callable(pw) else lambda: pw
148
124
 
149
125
  # Returns (set_id, jar_path, plugin)
150
- def _build_one_plugin(self, plugin_id):
126
+ def _build_one_plugin(self, plugin_id: str) -> Tuple[str, Optional[Path], Optional[Plugin]]:
151
127
  for plugin_set in self._plugin_sets:
152
128
  if plugin_set.has_plugin(plugin_id):
153
- return (plugin_set.get_id(),
154
- *plugin_set.build_plugin(plugin_id,
155
- self._get_plugin_signing_keystore(),
156
- self._get_plugin_signing_alias(),
157
- self._get_plugin_signing_password()))
129
+ bp = plugin_set.build_plugin(plugin_id,
130
+ self._get_plugin_signing_keystore(),
131
+ self._get_plugin_signing_alias(),
132
+ self._get_plugin_signing_password())
133
+ return plugin_set.get_id(), bp[0] if bp else None, bp[1] if bp else None
158
134
  raise Exception(f'{plugin_id}: not found in any plugin set')
159
135
 
160
136
  # Returns list of (registry_id, layer_id, dst_path, plugin)
161
- def _deploy_one_plugin(self, src_jar, plugin_id, layer_ids, interactive=False):
137
+ def _deploy_one_plugin(self, src_jar: Path, plugin_id: str, layer_ids: List[str], interactive: bool=False) -> List[Tuple[str, str, Optional[Path], Optional[Plugin]]]:
162
138
  ret = list()
163
139
  for plugin_registry in self._plugin_registries:
164
140
  if plugin_registry.has_plugin(plugin_id):
165
141
  for layer_id in layer_ids:
166
142
  layer = plugin_registry.get_layer(layer_id)
167
143
  if layer is not None:
144
+ dp = layer.deploy_plugin(plugin_id,
145
+ src_jar,
146
+ interactive=interactive)
168
147
  ret.append((plugin_registry.get_id(),
169
148
  layer.get_id(),
170
- *layer.deploy_plugin(plugin_id,
171
- src_jar,
172
- interactive=interactive)))
149
+ dp[0] if dp else None,
150
+ dp[1] if dp else None))
173
151
  if len(ret) == 0:
174
152
  raise Exception(f'{src_jar}: {plugin_id} not declared in any plugin registry')
175
153
  return ret
176
154
 
177
- def _get_password(self):
155
+ def _get_password(self) -> Optional[str]:
178
156
  return self._password() if self._password else None
179
157
 
180
- def _get_plugin_signing_alias(self):
158
+ def _get_plugin_signing_alias(self) -> str:
181
159
  return self._plugin_signing_credentials['plugin-signing-alias']
182
160
 
183
- def _get_plugin_signing_keystore(self):
161
+ def _get_plugin_signing_keystore(self) -> str:
184
162
  return self._plugin_signing_credentials['plugin-signing-keystore']
185
163
 
186
- def _get_plugin_signing_password(self):
164
+ def _get_plugin_signing_password(self) -> str:
187
165
  return self._get_password()
166
+
167
+ @staticmethod
168
+ def default_plugin_registry_catalogs() -> List[Path]:
169
+ return TurtlesApp._default_files(TurtlesApp.PLUGIN_REGISTRY_CATALOG)
170
+
171
+ @staticmethod
172
+ def default_plugin_set_catalogs() -> List[Path]:
173
+ return TurtlesApp._default_files(TurtlesApp.PLUGIN_SET_CATALOG)
174
+
175
+ @staticmethod
176
+ def default_plugin_signing_credentials() -> List[Path]:
177
+ return TurtlesApp._default_files(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
178
+
179
+ @staticmethod
180
+ def _default_files(file_str) -> List[Path]:
181
+ return [dir_path.joinpath(file_str) for dir_path in TurtlesApp.CONFIG_DIRS]
182
+
183
+ @staticmethod
184
+ def _select_file(file_str, preselected: Optional[Union[Path, str]]=None) -> Path:
185
+ if preselected:
186
+ preselected = path(preselected)
187
+ if not preselected.is_file():
188
+ raise FileNotFoundError(str(preselected))
189
+ return preselected
190
+ choices = TurtlesApp._default_files(file_str)
191
+ ret = next(filter(lambda f: f.is_file(), choices), None)
192
+ if ret is None:
193
+ raise FileNotFoundError(' or '.join(map(str, choices)))
194
+ return ret