lockss-turtles 0.6.0.dev23__py3-none-any.whl → 0.6.0.dev25__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,7 +6,7 @@ registries.
6
6
  """
7
7
 
8
8
  #: This package's version.
9
- __version__ = '0.6.0-dev23'
9
+ __version__ = '0.6.0-dev25'
10
10
 
11
11
  #: This package's copyright.
12
12
  __copyright__ = '''
lockss/turtles/app.py CHANGED
@@ -28,80 +28,156 @@
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
- # Remove in Python 3.14
32
- # See https://stackoverflow.com/questions/33533148/how-do-i-type-hint-a-method-with-the-type-of-the-enclosing-class/33533514#33533514
31
+ """
32
+ Module to represent Turtles operations.
33
+ """
34
+
35
+ # Remove in Python 3.14; see https://stackoverflow.com/a/33533514
33
36
  from __future__ import annotations
34
37
 
38
+ # Remove in Python 3.11; see https://docs.python.org/3.11/library/exceptions.html#exception-groups
39
+ from exceptiongroup import ExceptionGroup
40
+
35
41
  from collections.abc import Callable, Iterable
36
42
  from pathlib import Path
37
- from typing import ClassVar, Literal, Optional, Union
43
+ from typing import ClassVar, Optional, Union
38
44
 
39
- from exceptiongroup import ExceptionGroup
40
45
  from lockss.pybasic.fileutil import path
41
- from pydantic import Field, ValidationError
46
+ from pydantic import ValidationError
42
47
  import xdg
43
48
  import yaml
44
49
 
45
50
  from .plugin import Plugin, PluginIdentifier
46
- from .plugin_registry import PluginRegistry, PluginRegistryCatalog, PluginRegistryCatalogKind, PluginRegistryKind, PluginRegistryLayerIdentifier
51
+ from .plugin_registry import PluginRegistry, PluginRegistryCatalog, PluginRegistryCatalogKind, PluginRegistryIdentifier, PluginRegistryKind, PluginRegistryLayerIdentifier
47
52
  from .plugin_set import PluginSet, PluginSetCatalog, PluginSetCatalogKind, PluginSetKind
48
- from .util import BaseModelWithRoot, PathOrStr
53
+ from .plugin_signing_credentials import PluginSigningCredentials, PluginSigningCredentialsKind
54
+ from .util import PathOrStr
49
55
 
50
56
 
57
+ #: Type alias for the result of a single plugin building operation.
58
+ #: First item (index 0): identifier of the plugin set that had the given plugin.
59
+ #: Second item (index 1): plugin JAR file path (or None if not built).
60
+ #: Third item (index 2): plugin object (or None if not built).
61
+ BuildPluginResult = tuple[str, Optional[Path], Optional[Plugin]]
51
62
 
52
- PluginSigningCredentialsKind = Literal['PluginSigningCredentials']
53
63
 
64
+ #: Type alias for the result of a single plugin deployment operation to a given
65
+ #: plugin registry layer.
66
+ #: First item (index 0):
67
+ #: Second item (index 1):
68
+ #: Third item (index 2): deployed JAR file path (or None if not deployed).
69
+ #: Fourth item (index 3): plugin object (or None if not deployed).
70
+ DeployPluginResult = tuple[PluginRegistryIdentifier, PluginRegistryLayerIdentifier, Optional[Path], Optional[Plugin]]
54
71
 
55
- class PluginSigningCredentials(BaseModelWithRoot):
56
- kind: PluginSigningCredentialsKind = Field(description="This object's kind")
57
- plugin_signing_keystore: str = Field(title='Plugin Signing Keystore', description='A path to the plugin signing keystore', alias='plugin-signing-keystore')
58
- plugin_signing_alias: str = Field(title='Plugin Signing Alias', description='The plugin signing alias to use', alias='plugin-signing-alias')
59
72
 
60
- def get_plugin_signing_alias(self) -> str:
61
- return self.plugin_signing_alias
62
-
63
- def get_plugin_signing_keystore(self) -> Path:
64
- return self.get_root().joinpath(self.plugin_signing_keystore)
65
-
66
-
67
- class TurtlesApp(object):
73
+ class Turtles(object):
74
+ """
75
+ A Turtles command object, which can be used to execute Turtles operations.
76
+ """
68
77
 
78
+ #: The name of a Turtles configuration directory.
69
79
  CONFIG_DIR_NAME: ClassVar[str] = 'lockss-turtles'
70
80
 
81
+ #: The Turtles configuration directory under ``$XDG_CONFIG_HOME`` (by
82
+ # default ``$HOME/.config``, which is typically ``/home/$USER/.config``).
71
83
  XDG_CONFIG_DIR: ClassVar[Path] = Path(xdg.xdg_config_home(), CONFIG_DIR_NAME)
72
84
 
73
- USR_CONFIG_DIR: ClassVar[Path] = Path('/usr/local/share', CONFIG_DIR_NAME)
74
-
85
+ #: The Turtles configuration directory under ``/etc``.
75
86
  ETC_CONFIG_DIR: ClassVar[Path] = Path('/etc', CONFIG_DIR_NAME)
76
87
 
88
+ #: The Turtles configuration directory under ``/usr/local/share``.
89
+ USR_CONFIG_DIR: ClassVar[Path] = Path('/usr/local/share', CONFIG_DIR_NAME)
90
+
91
+ #: The Turtles configuration directories in order of preference:
92
+ #: ``XDG_CONFIG_DIR``, ``ETC_CONFIG_DIR``, ``USR_CONFIG_DIR``
77
93
  CONFIG_DIRS: ClassVar[tuple[Path, ...]] = (XDG_CONFIG_DIR, ETC_CONFIG_DIR, USR_CONFIG_DIR)
78
94
 
95
+ #: The default plugin registry catalog file name.
79
96
  PLUGIN_REGISTRY_CATALOG: ClassVar[str] = 'plugin-registry-catalog.yaml'
80
97
 
98
+ #: The default plugin set catalog file name.
81
99
  PLUGIN_SET_CATALOG: ClassVar[str] = 'plugin-set-catalog.yaml'
82
100
 
101
+ #: The default plugin signing credentials file name.
83
102
  PLUGIN_SIGNING_CREDENTIALS: ClassVar[str] = 'plugin-signing-credentials.yaml'
84
103
 
85
104
  def __init__(self) -> None:
105
+ """
106
+ Constructor.
107
+ """
86
108
  super().__init__()
87
- self._password: Optional[Callable[[], str]] = None
109
+ self._plugin_signing_password_callable: Optional[Callable[[], str]] = None
88
110
  self._plugin_registries: list[PluginRegistry] = list()
89
111
  self._plugin_registry_catalogs: list[PluginRegistryCatalog] = list()
90
112
  self._plugin_set_catalogs: list[PluginSetCatalog] = list()
91
113
  self._plugin_sets: list[PluginSet] = list()
92
114
  self._plugin_signing_credentials: Optional[PluginSigningCredentials] = None
93
115
 
94
- def build_plugin(self, plugin_ids: list[PluginIdentifier]) -> dict[str, tuple[str, Path, Plugin]]:
116
+ def build_plugin(self,
117
+ plugin_id_or_plugin_ids: Union[PluginIdentifier, list[PluginIdentifier]]) -> dict[str, BuildPluginResult]:
118
+ """
119
+ Builds zero or more plugins.
120
+
121
+ :param plugin_id_or_plugin_ids: Either one plugin identifier, or a list
122
+ of plugin identifiers.
123
+ :type plugin_id_or_plugin_ids: Union[PluginIdentifier, list[PluginIdentifier]]
124
+ :return: A mapping from plugin identifier to build plugin result; if no
125
+ plugin identifiers were given, the result is an empty mapping.
126
+ :rtype: dict[str, BuildPluginResult]
127
+ :raises Exception: If a given plugin identifier is not found in any
128
+ loaded plugin set.
129
+ """
130
+ plugin_ids: list[PluginIdentifier] = plugin_id_or_plugin_ids if isinstance(plugin_id_or_plugin_ids, list) else [plugin_id_or_plugin_ids]
95
131
  return {plugin_id: self._build_one_plugin(plugin_id) for plugin_id in plugin_ids}
96
132
 
97
- def deploy_plugin(self, src_paths: list[Path], layer_ids: list[PluginRegistryLayerIdentifier], interactive: bool=False) -> dict[tuple[Path, str], list[tuple[str, str, Optional[Path], Optional[Plugin]]]]:
98
- plugin_ids = [Plugin.id_from_jar(src_path) for src_path in src_paths]
133
+ def deploy_plugin(self,
134
+ src_path_or_src_paths: Union[Path, list[Path]],
135
+ layer_id_or_layer_ids: Union[PluginRegistryLayerIdentifier, list[PluginRegistryLayerIdentifier]],
136
+ interactive: bool=False) -> dict[tuple[Path, PluginIdentifier], list[DeployPluginResult]]:
137
+ """
138
+ Deploys zero or more plugins.
139
+
140
+ :param src_path_or_src_paths: Either one signed JAR file paths or a list
141
+ of signed JAR file paths.
142
+ :type src_path_or_src_paths: Union[Path, list[Path]]
143
+ :param layer_id_or_layer_ids: Either one plugin registry layer
144
+ identifier or a list of plugin registry
145
+ layer identifiers.
146
+ :type layer_id_or_layer_ids: Union[PluginRegistryLayerIdentifier, list[PluginRegistryLayerIdentifier]]
147
+ :param interactive: Whether interactive prompts are allowed (default
148
+ False).
149
+ :type interactive: bool
150
+ :return: A mapping from tuples of signed JAR file path and corresponding
151
+ plugin identifier to a list of build deployment results (one
152
+ per plugin registry layer); if no signed JAR file paths were
153
+ given, the result is an empty mapping.
154
+ :rtype: dict[tuple[Path, PluginIdentifier], list[DeployPluginResult]]
155
+ :raises Exception: If a given plugin is not declared in any loaded
156
+ plugin registry.
157
+ """
158
+ src_paths: list[Path] = src_path_or_src_paths if isinstance(src_path_or_src_paths, list) else [src_path_or_src_paths]
159
+ layer_ids: list[PluginRegistryLayerIdentifier] = layer_id_or_layer_ids if isinstance(layer_id_or_layer_ids, list) else [layer_id_or_layer_ids]
160
+ plugin_ids = [Plugin.id_from_jar(src_path) for src_path in src_paths] # FIXME: should go down to _deploy_one_plugin?
99
161
  return {(src_path, plugin_id): self._deploy_one_plugin(src_path,
100
162
  plugin_id,
101
163
  layer_ids,
102
164
  interactive=interactive) for src_path, plugin_id in zip(src_paths, plugin_ids)}
103
165
 
104
- def load_plugin_registries(self, plugin_registry_path_or_str: PathOrStr) -> TurtlesApp:
166
+ def load_plugin_registries(self,
167
+ plugin_registry_path_or_str: PathOrStr) -> Turtles:
168
+ """
169
+ Processes the given YAML file, loading all plugin registry definitions
170
+ it contains, ignoring other YAML objects.
171
+
172
+ :param plugin_registry_path_or_str: A file path (or string).
173
+ :type plugin_registry_path_or_str: PathOrStr
174
+ :return: This Turtles object (for chaining).
175
+ :rtype: Turtles
176
+ :raises ExceptionGroup: If one or more errors occur while loading plugin
177
+ registry definitions.
178
+ :raises ValueError: If the given file has already been processed or if
179
+ it contains no plugin registry definitions.
180
+ """
105
181
  plugin_registry_path = path(plugin_registry_path_or_str)
106
182
  if plugin_registry_path in map(lambda pr: pr.get_root(), self._plugin_registries):
107
183
  raise ValueError(f'Plugin registries already loaded from: {plugin_registry_path!s}')
@@ -121,7 +197,23 @@ class TurtlesApp(object):
121
197
  raise ValueError(f'No plugin registries found in: {plugin_registry_path!s}')
122
198
  return self
123
199
 
124
- def load_plugin_registry_catalogs(self, plugin_registry_catalog_path_or_str: PathOrStr) -> TurtlesApp:
200
+ def load_plugin_registry_catalogs(self,
201
+ plugin_registry_catalog_path_or_str: PathOrStr) -> Turtles:
202
+ """
203
+ Processes the given YAML file, loading all plugin registry catalog
204
+ definitions it contains and in turn all plugin registry definitions they
205
+ reference, ignoring other YAML objects.
206
+
207
+ :param plugin_registry_catalog_path_or_str: A file path (or string).
208
+ :type plugin_registry_catalog_path_or_str: PathOrStr
209
+ :return: This Turtles object (for chaining).
210
+ :rtype: Turtles
211
+ :raises ExceptionGroup: If one or more errors occur while loading plugin
212
+ registry catalog definitions or the plugin
213
+ registry definitions they reference.
214
+ :raises ValueError: If the given file has already been processed or if
215
+ it contains no plugin registry catalog definitions.
216
+ """
125
217
  plugin_registry_catalog_path = path(plugin_registry_catalog_path_or_str)
126
218
  if plugin_registry_catalog_path in map(lambda prc: prc.get_root(), self._plugin_registry_catalogs):
127
219
  raise ValueError(f'Plugin registry catalogs already loaded from: {plugin_registry_catalog_path!s}')
@@ -148,7 +240,23 @@ class TurtlesApp(object):
148
240
  raise ValueError(f'No plugin registry catalogs found in: {plugin_registry_catalog_path!s}')
149
241
  return self
150
242
 
151
- def load_plugin_set_catalogs(self, plugin_set_catalog_path_or_str: PathOrStr) -> TurtlesApp:
243
+ def load_plugin_set_catalogs(self,
244
+ plugin_set_catalog_path_or_str: PathOrStr) -> Turtles:
245
+ """
246
+ Processes the given YAML file, loading all plugin set catalog
247
+ definitions it contains and in turn all plugin set definitions they
248
+ reference, ignoring other YAML objects.
249
+
250
+ :param plugin_set_catalog_path_or_str: A file path (or string).
251
+ :type plugin_set_catalog_path_or_str: PathOrStr
252
+ :return: This Turtles object (for chaining).
253
+ :rtype: Turtles
254
+ :raises ExceptionGroup: If one or more errors occur while loading plugin
255
+ set catalog definitions or the plugin set
256
+ definitions they reference.
257
+ :raises ValueError: If the given file has already been processed or if
258
+ it contains no plugin set catalog definitions.
259
+ """
152
260
  plugin_set_catalog_path = path(plugin_set_catalog_path_or_str)
153
261
  if plugin_set_catalog_path in map(lambda psc: psc.get_root(), self._plugin_set_catalogs):
154
262
  raise ValueError(f'Plugin set catalogs already loaded from: {plugin_set_catalog_path!s}')
@@ -175,7 +283,21 @@ class TurtlesApp(object):
175
283
  raise ValueError(f'No plugin set catalogs found in: {plugin_set_catalog_path!s}')
176
284
  return self
177
285
 
178
- def load_plugin_sets(self, plugin_set_path_or_str: PathOrStr) -> TurtlesApp:
286
+ def load_plugin_sets(self,
287
+ plugin_set_path_or_str: PathOrStr) -> Turtles:
288
+ """
289
+ Processes the given YAML file, loading all plugin set definitions it
290
+ contains, ignoring other YAML objects.
291
+
292
+ :param plugin_set_path_or_str: A file path (or string).
293
+ :type plugin_set_path_or_str: PathOrStr
294
+ :return: This Turtles object (for chaining).
295
+ :rtype: Turtles
296
+ :raises ExceptionGroup: If one or more errors occur while loading plugin
297
+ set definitions.
298
+ :raises ValueError: If the given file has already been processed or if
299
+ it contains no plugin set definitions.
300
+ """
179
301
  plugin_set_path = path(plugin_set_path_or_str)
180
302
  if plugin_set_path in map(lambda ps: ps.get_root(), self._plugin_sets):
181
303
  raise ValueError(f'Plugin sets already loaded from: {plugin_set_path!s}')
@@ -195,7 +317,23 @@ class TurtlesApp(object):
195
317
  raise ValueError(f'No plugin sets found in: {plugin_set_path!s}')
196
318
  return self
197
319
 
198
- def load_plugin_signing_credentials(self, plugin_signing_credentials_path_or_str: PathOrStr) -> TurtlesApp:
320
+ def load_plugin_signing_credentials(self,
321
+ plugin_signing_credentials_path_or_str: PathOrStr) -> Turtles:
322
+ """
323
+ Processes the given YAML file, loading all plugin set definitions it
324
+ contains in search of exactly one, ignoring YAML objects of other kinds.
325
+
326
+ :param plugin_signing_credentials_path_or_str: A file path (or string).
327
+ :type plugin_signing_credentials_path_or_str: PathOrStr
328
+ :return: This Turtles object (for chaining).
329
+ :rtype: Turtles
330
+ :raises ExceptionGroup: If one or more errors occur while loading plugin
331
+ signing credentials definitions.
332
+ :raises ValueError: If the given file has already been processed, if it
333
+ contains no plugin signing credentials definitions,
334
+ or if it contains more than one plugin signing
335
+ credentials definitions.
336
+ """
199
337
  plugin_signing_credentials_path = path(plugin_signing_credentials_path_or_str)
200
338
  if self._plugin_signing_credentials:
201
339
  raise ValueError(f'Plugin signing credentials already loaded from: {self._plugin_signing_credentials.get_root()!s}')
@@ -216,7 +354,31 @@ class TurtlesApp(object):
216
354
  raise ValueError(f'Multiple plugin signing credentials found in: {plugin_signing_credentials_path!s}')
217
355
  return self
218
356
 
219
- def release_plugin(self, plugin_ids: list[PluginIdentifier], layer_ids: list[PluginRegistryLayerIdentifier], interactive: bool=False) -> dict[str, list[tuple[str, str, Path, Plugin]]]:
357
+ def release_plugin(self,
358
+ plugin_id_or_plugin_ids: Union[PluginIdentifier, list[PluginIdentifier]],
359
+ layer_id_or_layer_ids: Union[PluginRegistryLayerIdentifier, list[PluginRegistryLayerIdentifier]],
360
+ interactive: bool=False) -> dict[PluginIdentifier, list[DeployPluginResult]]:
361
+ """
362
+ Releases (builds then deploys) zero or more plugins.
363
+
364
+ :param plugin_id_or_plugin_ids: Either one plugin identifier, or a list
365
+ of plugin identifiers.
366
+ :type plugin_id_or_plugin_ids: Union[PluginIdentifier, list[PluginIdentifier]]
367
+ :param layer_id_or_layer_ids: Either one plugin registry layer
368
+ identifier or a list of plugin registry
369
+ layer identifiers.
370
+ :type layer_id_or_layer_ids: Union[PluginRegistryLayerIdentifier, list[PluginRegistryLayerIdentifier]]
371
+ :param interactive: Whether interactive prompts are allowed (default
372
+ False).
373
+ :type interactive: bool
374
+ :return: A mapping from plugin identifier to plugin deployment result;
375
+ if no plugins were given, the result is an empty mapping.
376
+ :rtype: dict[PluginIdentifier, list[DeployPluginResult]]
377
+ :raises Exception: If a given plugin is not found in any plugin set or
378
+ is not declared in any loaded plugin registry.
379
+ """
380
+ plugin_ids: list[PluginIdentifier] = plugin_id_or_plugin_ids if isinstance(plugin_id_or_plugin_ids, list) else [plugin_id_or_plugin_ids]
381
+ layer_ids: list[PluginRegistryLayerIdentifier] = layer_id_or_layer_ids if isinstance(layer_id_or_layer_ids, list) else [layer_id_or_layer_ids]
220
382
  # ... plugin_id -> (set_id, jar_path, plugin)
221
383
  ret1 = self.build_plugin(plugin_ids)
222
384
  jar_paths = [jar_path for set_id, jar_path, plugin in ret1.values()]
@@ -226,10 +388,30 @@ class TurtlesApp(object):
226
388
  interactive=interactive)
227
389
  return {plugin_id: val for (jar_path, plugin_id), val in ret2.items()}
228
390
 
229
- def set_password(self, pw: Union[Callable[[], str], str]) -> None:
230
- self._password = pw if callable(pw) else lambda: pw
231
-
232
- def _build_one_plugin(self, plugin_id: str) -> tuple[str, Optional[Path], Optional[Plugin]]:
391
+ def set_plugin_signing_password(self,
392
+ callable_or_password: Union[str, Callable[[], str]]) -> None:
393
+ """
394
+ Sets the plugin signing password callable.
395
+
396
+ :param callable_or_password: A callable returning a string (or simply a
397
+ string).
398
+ :type callable_or_password: Union[str, Callable[[], str]]
399
+ """
400
+ self._plugin_signing_password_callable = callable_or_password if callable(callable_or_password) else lambda: callable_or_password
401
+
402
+ def _build_one_plugin(self,
403
+ plugin_id: PluginIdentifier) -> BuildPluginResult:
404
+ """
405
+ Builds one plugin.
406
+
407
+ :param plugin_id: A plugin identifier.
408
+ :type plugin_id: PluginIdentifier
409
+ :return: A plugin build result object; if the plugin set returned None,
410
+ the second and third items (index 1 and 2) are None.
411
+ :rtype: BuildPluginResult
412
+ :raises Exception: If the given plugin identifier is not found in any
413
+ loaded plugin set.
414
+ """
233
415
  for plugin_set in self._plugin_sets:
234
416
  if plugin_set.has_plugin(plugin_id):
235
417
  bp = plugin_set.build_plugin(plugin_id,
@@ -237,15 +419,38 @@ class TurtlesApp(object):
237
419
  self._get_plugin_signing_alias(),
238
420
  self._get_plugin_signing_password())
239
421
  return plugin_set.get_id(), bp[0] if bp else None, bp[1] if bp else None
240
- raise Exception(f'{plugin_id}: not found in any plugin set')
241
-
242
- def _deploy_one_plugin(self, src_jar: Path, plugin_id: PluginIdentifier, layer_ids: list[PluginRegistryLayerIdentifier], interactive: bool=False) -> list[tuple[str, str, Optional[Path], Optional[Plugin]]]:
422
+ raise Exception(f'plugin identifier not found in any loaded plugin set: {plugin_id}')
423
+
424
+ def _deploy_one_plugin(self,
425
+ src_jar: Path,
426
+ plugin_id: PluginIdentifier,
427
+ layer_ids: list[PluginRegistryLayerIdentifier],
428
+ interactive: bool=False) -> list[DeployPluginResult]:
429
+ """
430
+ Deploys a single plugin to the provided plugin registry layers of all
431
+ loaded plugin registries that declare the given plugin.
432
+
433
+ :param src_jar: File path of the signed JAR.
434
+ :type src_jar: Path
435
+ :param plugin_id: The corresponding plugin identifier.
436
+ :type plugin_id: PluginIdentifier
437
+ :param layer_ids: A list of plugin layer identifiers.
438
+ :type layer_ids: list[PluginRegistryLayerIdentifier]
439
+ :param interactive: Whether interactive prompts are allowed (default
440
+ False).
441
+ :type interactive: bool
442
+ :return: A non-empty list of plugin deployment results; if for any, the
443
+ plugin registry returned None, the third and fourth items
444
+ (index 2 and 3) are None.
445
+ :rtype: list[DeployPluginResult]
446
+ :raises Exception: If the given plugin identifier is not declared in any
447
+ loaded plugin registry.
448
+ """
243
449
  ret = list()
244
450
  for plugin_registry in self._plugin_registries:
245
451
  if plugin_registry.has_plugin(plugin_id):
246
452
  for layer_id in layer_ids:
247
- layer = plugin_registry.get_layer(layer_id)
248
- if layer is not None:
453
+ if layer := plugin_registry.get_layer(layer_id):
249
454
  dp = layer.deploy_plugin(plugin_id,
250
455
  src_jar,
251
456
  interactive=interactive)
@@ -257,48 +462,136 @@ class TurtlesApp(object):
257
462
  raise Exception(f'{src_jar}: {plugin_id} not declared in any plugin registry')
258
463
  return ret
259
464
 
260
- def _get_password(self) -> Optional[str]:
261
- return self._password() if self._password else None
262
-
263
465
  def _get_plugin_signing_alias(self) -> str:
466
+ """
467
+ Returns the plugin signing alias from the loaded plugin signing
468
+ credentials.
469
+
470
+ :return: The plugin signing alias.
471
+ :rtype: str
472
+ """
264
473
  return self._plugin_signing_credentials.get_plugin_signing_alias()
265
474
 
266
475
  def _get_plugin_signing_keystore(self) -> Path:
476
+ """
477
+ Returns the plugin signing keystore file path from the loaded plugin
478
+ signing credentials.
479
+
480
+ :return: The plugin signing keystore file path.
481
+ :rtype: Path
482
+ """
267
483
  return self._plugin_signing_credentials.get_plugin_signing_keystore()
268
484
 
269
- def _get_plugin_signing_password(self) -> str:
270
- return self._get_password()
485
+ def _get_plugin_signing_password(self) -> Optional[Callable[[], str]]:
486
+ """
487
+ Returns the plugin signing password.
488
+
489
+ :return: The plugin signing password callable.
490
+ :rtype: Optional[Callable[[], str]]
491
+ """
492
+ return self._plugin_signing_password_callable
271
493
 
272
494
  @staticmethod
273
495
  def default_plugin_registry_catalog_choices() -> tuple[Path, ...]:
274
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_REGISTRY_CATALOG)
496
+ """
497
+ Returns the tuple of default plugin registry catalog file choices.
498
+
499
+ See ``CONFIG_DIRS`` and ``PLUGIN_REGISTRY_CATALOG``.
500
+
501
+ :return: A tuple of default plugin registry catalog file choices.
502
+ :rtype: tuple[Path, ...]
503
+ """
504
+ return Turtles._default_files(Turtles.PLUGIN_REGISTRY_CATALOG)
275
505
 
276
506
  @staticmethod
277
507
  def default_plugin_set_catalog_choices() -> tuple[Path, ...]:
278
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_SET_CATALOG)
508
+ """
509
+ Returns the tuple of default plugin set catalog file choices.
510
+
511
+ See ``CONFIG_DIRS`` and ``PLUGIN_SET_CATALOG``.
512
+
513
+ :return: A tuple of default plugin set catalog file choices.
514
+ :rtype: tuple[Path, ...]
515
+ """
516
+ return Turtles._default_files(Turtles.PLUGIN_SET_CATALOG)
279
517
 
280
518
  @staticmethod
281
519
  def default_plugin_signing_credentials_choices() -> tuple[Path, ...]:
282
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
520
+ """
521
+ Returns the tuple of default plugin signing credentials file choices.
522
+
523
+ See ``CONFIG_DIRS`` and ``PLUGIN_SIGNING_CREDENTIALS``.
524
+
525
+ :return: A tuple of default plugin signing credentials file choices.
526
+ :rtype: tuple[Path, ...]
527
+ """
528
+ return Turtles._default_files(Turtles.PLUGIN_SIGNING_CREDENTIALS)
283
529
 
284
530
  @staticmethod
285
531
  def select_default_plugin_registry_catalog() -> Optional[Path]:
286
- return TurtlesApp._select_file(TurtlesApp.default_plugin_registry_catalog_choices())
532
+ """
533
+ Of the default plugin registry catalog file choices, select the first
534
+ one that exists.
535
+
536
+ See ``default_plugin_registry_catalog_choices`` and ``_select_file``.
537
+
538
+ :return: The first of the default plugin registry catalog file choices
539
+ that exists, or None if none do.
540
+ :rtype: Optional[Path]
541
+ """
542
+ return Turtles._select_file(Turtles.default_plugin_registry_catalog_choices())
287
543
 
288
544
  @staticmethod
289
545
  def select_default_plugin_set_catalog() -> Optional[Path]:
290
- return TurtlesApp._select_file(TurtlesApp.default_plugin_set_catalog_choices())
546
+ """
547
+ Of the default plugin set catalog file choices, select the first one
548
+ that exists.
549
+
550
+ See ``default_plugin_registry_set_choices`` and ``_select_file``.
551
+
552
+ :return: The first of the default plugin set catalog file choices that
553
+ exists, or None if none do.
554
+ :rtype: Optional[Path]
555
+ """
556
+ return Turtles._select_file(Turtles.default_plugin_set_catalog_choices())
291
557
 
292
558
  @staticmethod
293
559
  def select_default_plugin_signing_credentials() -> Optional[Path]:
294
- return TurtlesApp._select_file(TurtlesApp.default_plugin_signing_credentials_choices())
560
+ """
561
+ Of the default plugin signing credentials file choices, select the first
562
+ one that exists.
563
+
564
+ See ``default_plugin_registry_set_choices`` and ``_select_file``.
565
+
566
+ :return: The first of the default plugin signing credentials file
567
+ choices that exists, or None if none do.
568
+ :rtype: Optional[Path]
569
+ """
570
+ return Turtles._select_file(Turtles.default_plugin_signing_credentials_choices())
295
571
 
296
572
  @staticmethod
297
573
  def _default_files(file_str) -> tuple[Path, ...]:
298
- return tuple(dir_path.joinpath(file_str) for dir_path in TurtlesApp.CONFIG_DIRS)
574
+ """
575
+ Given a file base name, returns a tuple of this file in the various
576
+ Turtles configuration directories (``CONFIG_DIRS``).
577
+
578
+ :param file_str: A file base name.
579
+ :type file_str: str
580
+ :return: The file in the various Turtles configuration directories.
581
+ :rtype: tuple[Path, ...]
582
+ """
583
+ return tuple(dir_path.joinpath(file_str) for dir_path in Turtles.CONFIG_DIRS)
299
584
 
300
585
  @staticmethod
301
586
  def _select_file(choices: Iterable[Path]) -> Optional[Path]:
587
+ """
588
+ Of the given files, returns the first one that exists.
589
+
590
+ :param choices: An iterable of file paths.
591
+ :type choices: Iterable[Path]
592
+ :return: The first choice that exists, or None if none do.
593
+ :rtype: Optional[Path]
594
+ """
302
595
  for p in choices:
303
596
  if p.is_file():
304
597
  return p