lockss-turtles 0.6.0.dev20__py3-none-any.whl → 0.6.0.dev22__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.
@@ -5,12 +5,15 @@ Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin
5
5
  registries.
6
6
  """
7
7
 
8
- __version__ = '0.6.0-dev20'
8
+ #: This package's version.
9
+ __version__ = '0.6.0-dev22'
9
10
 
11
+ #: This package's copyright.
10
12
  __copyright__ = '''
11
13
  Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
12
14
  '''.strip()
13
15
 
16
+ #: This package's license.
14
17
  __license__ = __copyright__ + '\n\n' + '''
15
18
  Redistribution and use in source and binary forms, with or without
16
19
  modification, are permitted provided that the following conditions are met:
lockss/turtles/cli.py CHANGED
@@ -144,22 +144,18 @@ class PluginJarOptions(BaseModel):
144
144
 
145
145
 
146
146
  class NonInteractiveOptions(BaseModel):
147
- non_interactive: Optional[bool] = Field(False, description='(plugin signing credentials) disallow interactive prompts')
147
+ non_interactive: Optional[bool] = Field(False,
148
+ description='(plugin signing credentials) disallow interactive prompts')
148
149
 
149
150
 
150
- class BuildPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginBuildingOptions, PluginIdentifierOptions):
151
- pass
152
-
153
-
154
- class DeployPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginJarOptions):
155
- pass
151
+ class TurtlesCommand(BaseModel):
156
152
 
153
+ class BuildPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginBuildingOptions, PluginIdentifierOptions): pass
157
154
 
158
- class ReleasePluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginBuildingOptions, PluginIdentifierOptions):
159
- pass
155
+ class DeployPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginJarOptions): pass
160
156
 
157
+ class ReleasePluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginBuildingOptions, PluginIdentifierOptions): pass
161
158
 
162
- class TurtlesCommand(BaseModel):
163
159
  bp: Optional[BuildPluginCommand] = Field(description='synonym for: build-plugin')
164
160
  build_plugin: Optional[BuildPluginCommand] = Field(description='build plugins', alias='build-plugin')
165
161
  copyright: Optional[StringCommand.type(__copyright__)] = Field(description=COPYRIGHT_DESCRIPTION)
@@ -237,10 +233,10 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
237
233
  # if len(result) > 0:
238
234
  # self._tabulate(title, result, headers)
239
235
 
240
- def _bp(self, build_plugin_command: BuildPluginCommand) -> None:
236
+ def _bp(self, build_plugin_command: TurtlesCommand.BuildPluginCommand) -> None:
241
237
  return self._build_plugin(build_plugin_command)
242
238
 
243
- def _build_plugin(self, build_plugin_command: BuildPluginCommand) -> None:
239
+ def _build_plugin(self, build_plugin_command: TurtlesCommand.BuildPluginCommand) -> None:
244
240
  errs = []
245
241
  for psc in build_plugin_command.get_plugin_set_catalogs():
246
242
  try:
@@ -276,7 +272,7 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
276
272
  def _copyright(self, string_command: StringCommand) -> None:
277
273
  self._do_string_command(string_command)
278
274
 
279
- def _deploy_plugin(self, deploy_plugin_command: DeployPluginCommand) -> None:
275
+ def _deploy_plugin(self, deploy_plugin_command: TurtlesCommand.DeployPluginCommand) -> None:
280
276
  errs = []
281
277
  for prc in deploy_plugin_command.get_plugin_registry_catalogs():
282
278
  try:
@@ -307,7 +303,7 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
307
303
  def _do_string_command(self, string_command: StringCommand) -> None:
308
304
  string_command()
309
305
 
310
- def _dp(self, deploy_plugin_command: DeployPluginCommand) -> None:
306
+ def _dp(self, deploy_plugin_command: TurtlesCommand.DeployPluginCommand) -> None:
311
307
  return self._deploy_plugin(deploy_plugin_command)
312
308
 
313
309
  def _license(self, string_command: StringCommand) -> None:
@@ -322,7 +318,7 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
322
318
  self._parser.error('no plugin signing password specified while in non-interactive mode')
323
319
  self._app.set_password(lambda: _p)
324
320
 
325
- def _release_plugin(self, release_plugin_command: ReleasePluginCommand) -> None:
321
+ def _release_plugin(self, release_plugin_command: TurtlesCommand.ReleasePluginCommand) -> None:
326
322
  errs = []
327
323
  for psc in release_plugin_command.get_plugin_set_catalogs():
328
324
  try:
@@ -371,7 +367,7 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
371
367
  headers=['Plugin identifier', 'Plugin version', 'Plugin registry', 'Plugin registry layer', 'Deployed JAR'],
372
368
  tablefmt=release_plugin_command.output_format))
373
369
 
374
- def _rp(self, release_plugin_command: ReleasePluginCommand) -> None:
370
+ def _rp(self, release_plugin_command: TurtlesCommand.ReleasePluginCommand) -> None:
375
371
  self._release_plugin(release_plugin_command)
376
372
 
377
373
  def _version(self, string_command: StringCommand) -> None:
lockss/turtles/plugin.py CHANGED
@@ -38,7 +38,7 @@ from __future__ import annotations
38
38
 
39
39
  from collections.abc import Callable
40
40
  from pathlib import Path
41
- from typing import Any, Optional
41
+ from typing import Any, AnyStr, IO, Optional
42
42
  import xml.etree.ElementTree as ET
43
43
  from zipfile import ZipFile
44
44
 
@@ -48,12 +48,32 @@ from lockss.pybasic.fileutil import path
48
48
  from .util import PathOrStr
49
49
 
50
50
 
51
+ #: A type alias for plugin identifiers.
51
52
  PluginIdentifier = str
52
53
 
53
54
 
54
55
  class Plugin(object):
56
+ """
57
+ An object to represent a LOCKSS plugin.
58
+ """
55
59
 
56
- def __init__(self, plugin_file, plugin_path) -> None:
60
+ def __init__(self, plugin_file: IO[AnyStr], plugin_path: PathOrStr) -> None:
61
+ """
62
+ Constructor.
63
+
64
+ Other exceptions than ``RuntimeError`` may be raised if the plugin
65
+ definition file cannot be parsed by ``xml.etree.ElementTree``.
66
+
67
+ :param plugin_file: An open file-like object that can read the plugin
68
+ definition file.
69
+ :type plugin_file: IO[AnyStr]
70
+ :param plugin_path: A string (or Path) representing the hierarchical
71
+ path of the plugin definition file (as a real file,
72
+ or as a file entry in a JAR file).
73
+ :type plugin_path: PathOrStr
74
+ :raises RuntimeError: If the plugin definition file parses as XML but
75
+ the top-level element is not <map>.
76
+ """
57
77
  super().__init__()
58
78
  self._path = plugin_path
59
79
  self._parsed = ET.parse(plugin_file).getroot()
@@ -62,6 +82,14 @@ class Plugin(object):
62
82
  raise RuntimeError(f'{plugin_path!s}: invalid root element: {tag}')
63
83
 
64
84
  def get_aux_packages(self) -> list[str]:
85
+ """
86
+ Returns the (possibly empty) list of auxiliary code packages declared by
87
+ the plugin (``plugin_aux_packages``).
88
+
89
+ :return: A non-null list of strings representing auxiliary code
90
+ packages.
91
+ :rtype: list[str]
92
+ """
65
93
  key = 'plugin_aux_packages'
66
94
  lst = [x[1] for x in self._parsed.findall('entry') if x[0].tag == 'string' and x[0].text == key]
67
95
  if lst is None or len(lst) < 1:
@@ -71,21 +99,76 @@ class Plugin(object):
71
99
  return [x.text for x in lst[0].findall('string')]
72
100
 
73
101
  def get_identifier(self) -> Optional[PluginIdentifier]:
102
+ """
103
+ Get this plugin's identifier (``plugin_identifier``).
104
+
105
+ :return: A plugin identifier, or None if missing.
106
+ :rtype: Optional[PluginIdentifier]
107
+ :raises ValueError: If the plugin definition contains more than one.
108
+ """
74
109
  return self._only_one('plugin_identifier')
75
110
 
76
111
  def get_name(self) -> Optional[str]:
112
+ """
113
+ Get this plugin's name (``plugin_name``).
114
+
115
+ :return: A plugin name, or None if missing.
116
+ :rtype: Optional[str]
117
+ :raises ValueError: If the plugin definition contains more than one.
118
+ """
77
119
  return self._only_one('plugin_name')
78
120
 
79
121
  def get_parent_identifier(self) -> Optional[PluginIdentifier]:
122
+ """
123
+ Get this plugin's parent identifier (``plugin_parent``).
124
+
125
+ :return: A parent plugin identifier, or None if this plugin has no
126
+ parent.
127
+ :rtype: Optional[PluginIdentifier]
128
+ :raises ValueError: If the plugin definition contains more than one.
129
+ """
80
130
  return self._only_one('plugin_parent')
81
131
 
82
132
  def get_parent_version(self) -> Optional[int]:
133
+ """
134
+ Get this plugin's parent version (``plugin_parent_version``).
135
+
136
+ :return: A parent plugin version, or None if this plugin has no
137
+ parent.
138
+ :rtype: Optional[int]
139
+ :raises ValueError: If the plugin definition contains more than one.
140
+ """
83
141
  return self._only_one('plugin_parent_version', int)
84
142
 
85
143
  def get_version(self) -> Optional[int]:
144
+ """
145
+ Get this plugin's version (``plugin_version``).
146
+
147
+ :return: A plugin version, or None if missing.
148
+ :rtype: Optional[int]
149
+ :raises ValueError: If the plugin definition contains more than one.
150
+ """
86
151
  return self._only_one('plugin_version', int)
87
152
 
88
- def _only_one(self, key: str, result: Callable=str) -> Optional[Any]:
153
+ def _only_one(self, key: str, result: Callable[[str], Any]=str) -> Optional[Any]:
154
+ """
155
+ Retrieves the value of a given key in the plugin definition, optionally
156
+ coerced into a representation (by default simply a string).
157
+
158
+ :param key: A plugin key.
159
+ :param key: str
160
+ :param result: A functor that takes a string and returns the desired
161
+ representation; by default this is the string constructor
162
+ ``str``, meaning by default the string values are
163
+ returned unchanged.
164
+ :param result: Callable[[str], Any]
165
+ :return: The value for the given key, coerced through the given functor,
166
+ or None if the plugin definition does not contain any entry
167
+ with the given key.
168
+ :rtype: Optional[Any]
169
+ :raises ValueError: If the plugin definition contains more than one
170
+ entry with the given key.
171
+ """
89
172
  lst = [x[1].text for x in self._parsed.findall('entry') if x[0].tag == 'string' and x[0].text == key]
90
173
  if lst is None or len(lst) < 1:
91
174
  return None
@@ -94,8 +177,16 @@ class Plugin(object):
94
177
  return result(lst[0])
95
178
 
96
179
  @staticmethod
97
- def from_jar(jar_path: PathOrStr) -> Plugin:
98
- jar_path = path(jar_path) # in case it's a string
180
+ def from_jar(jar_path_or_str: PathOrStr) -> Plugin:
181
+ """
182
+ Instantiates a Plugin object from the given plugin JAR file.
183
+
184
+ :param jar_path_or_str: The path to a plugin JAR.
185
+ :type jar_path_or_str: PathOrStr
186
+ :return: A Plugin object.
187
+ :rtype: Plugin
188
+ """
189
+ jar_path = path(jar_path_or_str)
99
190
  plugin_id = Plugin.id_from_jar(jar_path)
100
191
  plugin_fstr = str(Plugin.id_to_file(plugin_id))
101
192
  with ZipFile(jar_path, 'r') as zip_file:
@@ -103,18 +194,50 @@ class Plugin(object):
103
194
  return Plugin(plugin_file, plugin_fstr)
104
195
 
105
196
  @staticmethod
106
- def from_path(fpath: PathOrStr) -> Plugin:
107
- fpath = path(fpath) # in case it's a string
108
- with open(fpath, 'r') as input_file:
197
+ def from_path(path_or_str: PathOrStr) -> Plugin:
198
+ """
199
+ Instantiates a Plugin object from the given plugin file.
200
+
201
+ :param path_or_str: The path to a plugin file.
202
+ :type path_or_str: PathOrStr
203
+ :return: A Plugin object.
204
+ :rtype: Plugin
205
+ """
206
+ fpath = path(path_or_str)
207
+ with fpath.open('r') as input_file:
109
208
  return Plugin(input_file, fpath)
110
209
 
111
210
  @staticmethod
112
211
  def file_to_id(plugin_fstr: str) -> PluginIdentifier:
212
+ """
213
+ Converts a plugin file path (ending in ``.xml``) to the implied plugin
214
+ identifier (e.g. ``org/myproject/plugin/MyPlugin.xml`` implies
215
+ ``org.myproject.plugin.MyPlugin``).
216
+
217
+ See also ``id_to_file``.
218
+
219
+ :param plugin_fstr: A string file path.
220
+ :type plugin_fstr: str
221
+ :return: A plugin identifier.
222
+ :rtype: PluginIdentifier
223
+ """
113
224
  return plugin_fstr.replace('/', '.')[:-4] # 4 is len('.xml')
114
225
 
115
226
  @staticmethod
116
- def id_from_jar(jar_path: PathOrStr) -> PluginIdentifier:
117
- jar_path = path(jar_path) # in case it's a string
227
+ def id_from_jar(jar_path_or_str: PathOrStr) -> PluginIdentifier:
228
+ """
229
+ Extracts the plugin identifier from a plugin JAR's manifest file.
230
+
231
+ :param jar_path_or_str: The path to a plugin JAR.
232
+ :type jar_path_or_str: PathOrStr
233
+ :return: The plugin identifier extracted from the given plugin JAR's
234
+ manifest file.
235
+ :rtype: PluginIdentifier
236
+ :raises Exception: If the JAR's manifest file has no entry with
237
+ ``Lockss-Plugin`` equal to ``true`` and ``Name``
238
+ equal to the packaged plugin's identifier.
239
+ """
240
+ jar_path = path(jar_path_or_str)
118
241
  manifest = java_manifest.from_jar(jar_path)
119
242
  for entry in manifest:
120
243
  if entry.get('Lockss-Plugin') == 'true':
@@ -127,8 +250,29 @@ class Plugin(object):
127
250
 
128
251
  @staticmethod
129
252
  def id_to_dir(plugin_id: PluginIdentifier) -> Path:
253
+ """
254
+ Returns the path of the directory containing the given plugin identifier
255
+ (for example ``org/myproject/plugin`` for
256
+ ``org.myproject.plugin.MyPlugin``).
257
+
258
+ :param plugin_id: A plugin identifier.
259
+ :type plugin_id: PluginIdentifier
260
+ :return: The directory path containing the given plugin identifier.
261
+ :rtype: Path
262
+ """
130
263
  return Plugin.id_to_file(plugin_id).parent
131
264
 
132
265
  @staticmethod
133
266
  def id_to_file(plugin_id: PluginIdentifier) -> Path:
267
+ """
268
+ Returns the path of the definition file corresponding to the given
269
+ plugin identifier (for example ``org/myproject/plugin/MyPlugin.xml`` for
270
+ ``org.myproject.plugin.MyPlugin``).
271
+
272
+ :param plugin_id: A plugin identifier.
273
+ :type plugin_id: PluginIdentifier
274
+ :return: The path of the definition file corresponding to the given
275
+ plugin identifier.
276
+ :rtype: Path
277
+ """
134
278
  return Path(f'{plugin_id.replace(".", "/")}.xml')