lockss-turtles 0.6.0.dev18__tar.gz → 0.6.0.dev20__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lockss-turtles
3
- Version: 0.6.0.dev18
3
+ Version: 0.6.0.dev20
4
4
  Summary: Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin registries
5
5
  License: BSD-3-Clause
6
6
  Author: Thib Guicherd-Callin
@@ -34,7 +34,7 @@ Description-Content-Type: text/x-rst
34
34
  Turtles
35
35
  =======
36
36
 
37
- .. |RELEASE| replace:: 0.6.0-dev18
37
+ .. |RELEASE| replace:: 0.6.0-dev20
38
38
  .. |RELEASE_DATE| replace:: ?
39
39
 
40
40
  .. |HELP| replace:: ``--help/-h``
@@ -2,7 +2,7 @@
2
2
  Turtles
3
3
  =======
4
4
 
5
- .. |RELEASE| replace:: 0.6.0-dev18
5
+ .. |RELEASE| replace:: 0.6.0-dev20
6
6
  .. |RELEASE_DATE| replace:: ?
7
7
 
8
8
  .. |HELP| replace:: ``--help/-h``
@@ -28,7 +28,7 @@
28
28
 
29
29
  [project]
30
30
  name = "lockss-turtles"
31
- version = "0.6.0-dev18" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
31
+ version = "0.6.0-dev20" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
32
32
  description = "Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin registries"
33
33
  license = { text = "BSD-3-Clause" }
34
34
  readme = "README.rst"
@@ -5,7 +5,7 @@ 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-dev18'
8
+ __version__ = '0.6.0-dev20'
9
9
 
10
10
  __copyright__ = '''
11
11
  Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
@@ -74,7 +74,7 @@ class TurtlesApp(object):
74
74
 
75
75
  ETC_CONFIG_DIR: ClassVar[Path] = Path('/etc', CONFIG_DIR_NAME)
76
76
 
77
- CONFIG_DIRS: ClassVar[tuple[Path, ...]] = (XDG_CONFIG_DIR, USR_CONFIG_DIR, ETC_CONFIG_DIR)
77
+ CONFIG_DIRS: ClassVar[tuple[Path, ...]] = (XDG_CONFIG_DIR, ETC_CONFIG_DIR, USR_CONFIG_DIR)
78
78
 
79
79
  PLUGIN_REGISTRY_CATALOG: ClassVar[str] = 'plugin-registry-catalog.yaml'
80
80
 
@@ -33,6 +33,7 @@ Tool for managing LOCKSS plugin sets and LOCKSS plugin registries
33
33
  """
34
34
 
35
35
  from getpass import getpass
36
+ from itertools import chain
36
37
  from pathlib import Path
37
38
 
38
39
  from exceptiongroup import ExceptionGroup
@@ -51,10 +52,10 @@ from .util import file_or
51
52
 
52
53
 
53
54
  class PluginBuildingOptions(BaseModel):
54
- plugin_set: Optional[list[FilePath]] = Field(aliases=['-s'], description=f'(plugin sets) add one or more plugin set definition files to the loaded plugin sets')
55
- plugin_set_catalog: Optional[list[FilePath]] = Field(aliases=['-S'], description=f'(plugin sets) add one or more plugin set catalogs to the loaded plugin set catalogs; if no plugin set catalogs or plugin sets are specified, load {file_or(TurtlesApp.default_plugin_set_catalog_choices())}')
56
- plugin_signing_credentials: Optional[FilePath] = Field(aliases=['-c'], description=f'(plugin signing credentials) load the plugin signing credentials from the given file, or if none, from {file_or(TurtlesApp.default_plugin_signing_credentials_choices())}')
57
- plugin_signing_password: Optional[str] = Field(description='(plugin signing credentials) set the plugin signing password, or if none, prompt interactively')
55
+ plugin_set: Optional[list[FilePath]] = Field(aliases=['-s'], title='Plugin Sets', description=f'(plugin sets) add one or more plugin set definition files to the loaded plugin sets')
56
+ plugin_set_catalog: Optional[list[FilePath]] = Field(aliases=['-S'], title='Plugin Set Catalogs', description=f'(plugin sets) add one or more plugin set catalogs to the loaded plugin set catalogs; if no plugin set catalogs or plugin sets are specified, load {file_or(TurtlesApp.default_plugin_set_catalog_choices())}')
57
+ plugin_signing_credentials: Optional[FilePath] = Field(aliases=['-c'], title='Plugin Signing Credentials', description=f'(plugin signing credentials) load the plugin signing credentials from the given file, or if none, from {file_or(TurtlesApp.default_plugin_signing_credentials_choices())}')
58
+ plugin_signing_password: Optional[str] = Field(title='Plugin Signing Password', description='(plugin signing credentials) set the plugin signing password, or if none, prompt interactively')
58
59
 
59
60
  def get_plugin_sets(self) -> list[Path]:
60
61
  return [path(p) for p in self.plugin_set or []]
@@ -97,9 +98,9 @@ class PluginDeploymentOptions(BaseModel):
97
98
  raise FileNotFoundError(file_or(TurtlesApp.default_plugin_set_catalog_choices()))
98
99
 
99
100
  def get_plugin_registry_layers(self) -> list[PluginRegistryLayerIdentifier]:
100
- ret = [*(self.plugin_registry_layer or []), *[file_lines(file_path) for file_path in self.plugin_registry_layers or []]]
101
+ ret = [*(self.plugin_registry_layer or []), *chain.from_iterable(file_lines(file_path) for file_path in self.plugin_registry_layers or [])]
101
102
  for layer in reversed(['testing', 'production']):
102
- if getattr(self, layer, False):
103
+ if getattr(self, layer, False) and layer not in ret:
103
104
  ret.insert(0, layer)
104
105
  if ret:
105
106
  return ret
@@ -118,7 +119,7 @@ class PluginIdentifierOptions(BaseModel):
118
119
  return path(v)
119
120
 
120
121
  def get_plugin_identifiers(self) -> list[str]:
121
- ret = [*(self.plugin_identifier or []), *[file_lines(file_path) for file_path in self.plugin_identifiers or []]]
122
+ ret = [*(self.plugin_identifier or []), *chain.from_iterable(file_lines(file_path) for file_path in self.plugin_identifiers or [])]
122
123
  if ret:
123
124
  return ret
124
125
  raise ValueError('Empty list of plugin identifiers')
@@ -136,7 +137,7 @@ class PluginJarOptions(BaseModel):
136
137
  return path(v)
137
138
 
138
139
  def get_plugin_jars(self):
139
- ret = [*(self.plugin_jar or []), *[file_lines(file_path) for file_path in self.plugin_jars or []]]
140
+ ret = [*(self.plugin_jar or []), *chain.from_iterable(file_lines(file_path) for file_path in self.plugin_jars or [])]
140
141
  if len(ret):
141
142
  return ret
142
143
  raise ValueError('Empty list of plugin JARs')
@@ -160,7 +161,7 @@ class ReleasePluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDep
160
161
 
161
162
  class TurtlesCommand(BaseModel):
162
163
  bp: Optional[BuildPluginCommand] = Field(description='synonym for: build-plugin')
163
- build_plugin: Optional[BuildPluginCommand] = Field(description='build (package and sign) plugins', alias='build-plugin')
164
+ build_plugin: Optional[BuildPluginCommand] = Field(description='build plugins', alias='build-plugin')
164
165
  copyright: Optional[StringCommand.type(__copyright__)] = Field(description=COPYRIGHT_DESCRIPTION)
165
166
  deploy_plugin: Optional[DeployPluginCommand] = Field(description='deploy plugins', alias='deploy-plugin')
166
167
  dp: Optional[DeployPluginCommand] = Field(description='synonym for: deploy-plugin')
@@ -66,7 +66,7 @@ PluginRegistryLayoutFileNamingConvention = Literal['abbreviated', 'identifier',
66
66
  class BasePluginRegistryLayout(BaseModel, ABC):
67
67
  TYPE_FIELD: ClassVar[dict[str, str]] = dict(title='Plugin Registry Layout Type', description='A plugin registry layout type')
68
68
  FILE_NAMING_CONVENTION_DEFAULT: ClassVar[PluginRegistryLayoutFileNamingConvention] = 'identifier'
69
- FILE_NAMING_CONVENTION_FIELD: ClassVar[dict[str, str]] = dict(title='Plugin Registry Layout File Naming Convention', description='A file naming convention for the plugin registry layout', alias='file-naming-convention')
69
+ FILE_NAMING_CONVENTION_FIELD: ClassVar[dict[str, str]] = dict(alias='file-naming-convention', title='Plugin Registry Layout File Naming Convention', description='A file naming convention for the plugin registry layout')
70
70
 
71
71
  _plugin_registry: Optional[PluginRegistry]
72
72
 
@@ -213,13 +213,13 @@ PluginRegistryIdentifier = str
213
213
 
214
214
 
215
215
  class PluginRegistry(BaseModelWithRoot):
216
- kind: PluginRegistryKind = Field(description="This object's kind")
216
+ kind: PluginRegistryKind = Field(title='Kind', description="This object's kind")
217
217
  id: PluginRegistryIdentifier = Field(title='Plugin Registry Identifier', description='An identifier for the plugin set')
218
218
  name: str = Field(title='Plugin Registry Name', description='A name for the plugin set')
219
219
  layout: PluginRegistryLayout = Field(title='Plugin Registry Layout', description='A layout for the plugin registry')
220
220
  layers: list[PluginRegistryLayer] = Field(min_length=1, title='Plugin Registry Layers', description="A non-empty list of plugin registry layers")
221
- plugin_identifiers: list[PluginIdentifier] = Field(min_length=1, title='Plugin Identifiers', description="A non-empty list of plugin identifiers", alias='plugin-identifiers')
222
- suppressed_plugin_identifiers: list[PluginIdentifier] = Field([], title='Suppressed Plugin Identifiers', description="A list of suppressed plugin identifiers", alias='suppressed-plugin-identifiers')
221
+ plugin_identifiers: list[PluginIdentifier] = Field(alias='plugin-identifiers', min_length=1, title='Plugin Identifiers', description="A non-empty list of plugin identifiers")
222
+ suppressed_plugin_identifiers: list[PluginIdentifier] = Field([], alias='suppressed-plugin-identifiers', title='Suppressed Plugin Identifiers', description="A list of suppressed plugin identifiers")
223
223
 
224
224
  def get_id(self) -> PluginRegistryIdentifier:
225
225
  return self.id
@@ -59,14 +59,15 @@ class PluginSetCatalog(BaseModelWithRoot):
59
59
  plugin set catalog.
60
60
  """
61
61
  #: This object's kind.
62
- kind: PluginSetCatalogKind = Field(description="This object's kind")
62
+ kind: PluginSetCatalogKind = Field(title='Kind', description="This object's kind")
63
63
  #: A non-empty list of plugin set files.
64
- plugin_set_files: list[str] = Field(min_length=1, description="A non-empty list of plugin set files", title='Plugin Set Files', alias='plugin-set-files')
64
+ plugin_set_files: list[str] = Field(alias='plugin-set-files', min_length=1, title='Plugin Set Files', description="A non-empty list of plugin set files")
65
65
 
66
66
  def get_plugin_set_files(self) -> list[Path]:
67
67
  """
68
68
  Return this plugin set catalog's list of plugin set definition file
69
69
  paths (relative to the root if not absolute).
70
+
70
71
  :return: A list of plugin set definition file paths.
71
72
  :rtype: list[Path]
72
73
  """
@@ -80,7 +81,8 @@ PluginSetBuilderType = Literal['ant', 'maven']
80
81
  class BasePluginSetBuilder(BaseModelWithRoot, ABC):
81
82
  """
82
83
  An abstract Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to
83
- represent a plugin set builder.
84
+ represent a plugin set builder, with concrete implementations
85
+ ``AntPluginSetBuilder`` and ``MavenPluginSetBuilder``.
84
86
  """
85
87
 
86
88
  #: Pydantic definition of the ``type`` field.
@@ -112,10 +114,10 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
112
114
 
113
115
  def get_main(self) -> Path:
114
116
  """
115
- Returns the plugin set's main code path (relative to the root if not
116
- absolute).
117
+ Returns this plugin set builder's main code path (relative to the root
118
+ if not absolute).
117
119
 
118
- :return: The plugin set's main code path.
120
+ :return: This plugin set's main code path.
119
121
  :rtype: Path
120
122
  :raises ValueError: If this object is not properly initialized.
121
123
  """
@@ -123,10 +125,10 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
123
125
 
124
126
  def get_test(self) -> Path:
125
127
  """
126
- Returns the plugin set's unit test path (relative to the root if not
127
- absolute).
128
+ Returns this plugin set builder's unit test path (relative to the root
129
+ if not absolute).
128
130
 
129
- :return: The plugin set's unit test path.
131
+ :return: This plugin set's unit test path.
130
132
  :rtype: Path
131
133
  :raises ValueError: If this object is not properly initialized.
132
134
  """
@@ -166,7 +168,7 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
166
168
 
167
169
  def _get_main(self) -> str:
168
170
  """
169
- Return the concrete implementation's ``main`` field.
171
+ Returns the concrete implementation's ``main`` field.
170
172
 
171
173
  :return: The ``main`` field.
172
174
  :rtype: str
@@ -175,7 +177,7 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
175
177
 
176
178
  def _get_test(self) -> str:
177
179
  """
178
- Return the concrete implementation's ``test`` field.
180
+ Returns the concrete implementation's ``test`` field.
179
181
 
180
182
  :return: The ``test`` field.
181
183
  :rtype: str
@@ -196,13 +198,19 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
196
198
 
197
199
 
198
200
  class AntPluginSetBuilder(BasePluginSetBuilder):
201
+ #: Default value for the ``main`` field.
199
202
  DEFAULT_MAIN: ClassVar[str] = 'plugins/src'
203
+ #: Default value for the ``test`` field.
200
204
  DEFAULT_TEST: ClassVar[str] = 'plugins/test/src'
201
205
 
206
+ #: This plugin set builder's type.
202
207
  type: Literal['ant'] = Field(**BasePluginSetBuilder.TYPE_FIELD)
208
+ #: This plugin set builder's main code path.
203
209
  main: Optional[str] = Field(DEFAULT_MAIN, **BasePluginSetBuilder.MAIN_FIELD)
210
+ #: This plugin set builder's unit test path.
204
211
  test: Optional[str] = Field(DEFAULT_TEST, **BasePluginSetBuilder.TEST_FIELD)
205
212
 
213
+ #: An internal flag to remember if a build has occurred.
206
214
  _built: bool
207
215
 
208
216
  def build_plugin(self, plugin_id: PluginIdentifier, keystore_path: Path, keystore_alias: str, keystore_password=None) -> tuple[Path, Plugin]:
@@ -219,6 +227,9 @@ class AntPluginSetBuilder(BasePluginSetBuilder):
219
227
  self._built = False
220
228
 
221
229
  def _big_build(self) -> None:
230
+ """
231
+ Optionally performs the "big build".
232
+ """
222
233
  if not self._built:
223
234
  # Do build
224
235
  subprocess.run('ant load-plugins',
@@ -226,10 +237,26 @@ class AntPluginSetBuilder(BasePluginSetBuilder):
226
237
  self._built = True
227
238
 
228
239
  def _little_build(self, plugin_id: PluginIdentifier, keystore_path: Path, keystore_alias: str, keystore_password: str=None) -> tuple[Path, Plugin]:
240
+ """
241
+ Performs the "little build" of the given plugin.
242
+
243
+ :param plugin_id: A plugin identifier.
244
+ :type plugin_id: PluginIdentifier
245
+ :param keystore_path: The path to the plugin signing keystore.
246
+ :type keystore_path: Path
247
+ :param keystore_alias: The signing alias to use from the plugin signing
248
+ keystore.
249
+ :type keystore_alias: str
250
+ :param keystore_password: The signing password.
251
+ :type keystore_password: str
252
+ :return: A tuple of the plugin JAR path and the corresponding ``Plugin``
253
+ object.
254
+ :rtype: tuple[Path, Plugin]
255
+ """
229
256
  orig_plugin = None
230
257
  cur_id = plugin_id
231
258
  # Get all directories for jarplugin -d
232
- dirs = list()
259
+ dirs = []
233
260
  while cur_id is not None:
234
261
  cur_plugin = self.make_plugin(cur_id)
235
262
  orig_plugin = orig_plugin or cur_plugin
@@ -312,7 +339,7 @@ class MavenPluginSetBuilder(BasePluginSetBuilder):
312
339
  def _little_build(self, plugin_id: PluginIdentifier) -> tuple[Path, Plugin]:
313
340
  jar_path = self.get_root().joinpath('target', 'pluginjars', f'{plugin_id}.jar')
314
341
  if not jar_path.is_file():
315
- raise Exception(f'{plugin_id}: built JAR not found: {jar_path!s}')
342
+ raise FileNotFoundError(str(jar_path))
316
343
  return jar_path, Plugin.from_jar(jar_path)
317
344
 
318
345
  def _sanitize(self, called_process_error: subprocess.CalledProcessError) -> subprocess.CalledProcessError: