lockss-turtles 0.6.0.dev21__py3-none-any.whl → 0.6.0.dev23__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.
@@ -28,6 +28,10 @@
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
+ """
32
+ Library to represent plugin registries and plugin registry catalogs.
33
+ """
34
+
31
35
  # Remove in Python 3.14
32
36
  # See https://stackoverflow.com/questions/33533148/how-do-i-type-hint-a-method-with-the-type-of-the-enclosing-class/33533514#33533514
33
37
  from __future__ import annotations
@@ -45,66 +49,211 @@ from .plugin import Plugin, PluginIdentifier
45
49
  from .util import BaseModelWithRoot
46
50
 
47
51
 
52
+ #: A type alias for the plugin registry catalog kind.
48
53
  PluginRegistryCatalogKind = Literal['PluginRegistryCatalog']
49
54
 
50
55
 
51
56
  class PluginRegistryCatalog(BaseModelWithRoot):
52
- kind: PluginRegistryCatalogKind = Field(title='Kind', description="This object's kind")
53
- plugin_registry_files: list[str] = Field(alias='plugin-registry-files', min_length=1, title='Plugin Registry Files', description="A non-empty list of plugin registry files")
57
+ """
58
+ A Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to represent a
59
+ plugin registry catalog.
60
+ """
61
+
62
+ #: This object's kind.
63
+ kind: PluginRegistryCatalogKind = Field(title='Kind',
64
+ description="This object's kind")
65
+
66
+ #: A non-empty list of plugin registry files.
67
+ plugin_registry_files: list[str] = Field(alias='plugin-registry-files',
68
+ min_length=1,
69
+ title='Plugin Registry Files',
70
+ description="A non-empty list of plugin registry files")
54
71
 
55
72
  def get_plugin_registry_files(self) -> list[Path]:
73
+ """
74
+ Returns the list of plugin registry files in this catalog, relative to
75
+ the plugin registry catalog file if applicable.
76
+
77
+ :return: A non-null list of plugin registry file paths.
78
+ :rtype: list[Path]
79
+ """
56
80
  return [self.get_root().joinpath(pstr) for pstr in self.plugin_registry_files]
57
81
 
58
82
 
83
+ #: A type alias for the two plugin registry layout types.
59
84
  PluginRegistryLayoutType = Literal['directory', 'rcs']
60
85
 
61
86
 
87
+ #: A type alias for the three plugin registry layout file naming conventions.
62
88
  PluginRegistryLayoutFileNamingConvention = Literal['abbreviated', 'identifier', 'underscore']
63
89
 
64
90
 
65
91
  class BasePluginRegistryLayout(BaseModel, ABC):
66
- TYPE_FIELD: ClassVar[dict[str, str]] = dict(title='Plugin Registry Layout Type', description='A plugin registry layout type')
92
+ """
93
+ An abstract Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to
94
+ represent a plugin registry layout, with concrete implementations
95
+ ``DirectoryPluginRegistryLayout`` and ``RcsPluginRegistryLayout``.
96
+ """
97
+
98
+ #: Pydantic definition of the ``type`` field.
99
+ TYPE_FIELD: ClassVar[dict[str, str]] = dict(title='Plugin Registry Layout Type',
100
+ description='A plugin registry layout type')
101
+
102
+ #: Default file naming convention.
67
103
  FILE_NAMING_CONVENTION_DEFAULT: ClassVar[PluginRegistryLayoutFileNamingConvention] = 'identifier'
68
- 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')
69
104
 
105
+ #: Pydantic definition of the ``file_naming_convention`` field.
106
+ FILE_NAMING_CONVENTION_FIELD: ClassVar[dict[str, str]] = dict(alias='file-naming-convention',
107
+ title='Plugin Registry Layout File Naming Convention',
108
+ description='A file naming convention for the plugin registry layout')
109
+
110
+ #: Internal backreference to the enclosing plugin registry; see ``initialize``.
70
111
  _plugin_registry: Optional[PluginRegistry]
71
112
 
72
- def deploy_plugin(self, plugin_id: PluginIdentifier, layer: PluginRegistryLayer, src_path: Path, interactive: bool=False) -> Optional[tuple[Path, Plugin]]:
113
+ def deploy_plugin(self,
114
+ plugin_id: PluginIdentifier,
115
+ layer: PluginRegistryLayer,
116
+ src_path: Path,
117
+ interactive: bool=False) -> Optional[tuple[Path, Plugin]]:
118
+ """
119
+ Deploys the given plugin to the target plugin registry layer according to
120
+ this plugin registry layout's file naming convention.
121
+
122
+ See ``_copy_jar``.
123
+
124
+ :param plugin_id: A plugin identifier.
125
+ :type plugin_id: PluginIdentifier
126
+ :param layer: A plugin registry layer.
127
+ :type layer: PluginRegistryLayer
128
+ :param src_path: The path of the plugin JAR.
129
+ :type src_path: Path
130
+ :param interactive: If False (the default), no interactive confirmation
131
+ will occur. If True, and the given plugin is being
132
+ deployed to the target layer for the very first
133
+ time, the user will be prompted interactively to
134
+ confirm.
135
+ :type interactive: bool
136
+ :return: A tuple of the path of the deployed JAR and a Plugin object
137
+ instantiated from the source JAR, or None if the user was
138
+ prompted for confirmation and responded negatively.
139
+ :rtype: Optional[tuple[Path, Plugin]]
140
+ """
73
141
  src_path = path(src_path) # in case it's a string
74
142
  dst_path = self._get_dstpath(plugin_id, layer)
75
143
  if not self._proceed_copy(src_path, dst_path, layer, interactive=interactive):
76
144
  return None
77
- self._copy_jar(src_path, dst_path, interactive=interactive)
145
+ self._copy_jar(src_path, dst_path)
78
146
  return dst_path, Plugin.from_jar(src_path)
79
147
 
80
- def get_file_for(self, plugin_id, layer: PluginRegistryLayer) -> Optional[Path]:
81
- jar_path = self._get_dstpath(plugin_id, layer)
82
- return jar_path if jar_path.is_file() else None
148
+ # Believed to be abandoned:
149
+
150
+ # def get_file_for(self,
151
+ # plugin_id,
152
+ # layer: PluginRegistryLayer) -> Optional[Path]:
153
+ # """
154
+ #
155
+ # :param plugin_id:
156
+ # :type plugin_id:
157
+ # :param layer:
158
+ # :type layer:
159
+ # :return:
160
+ # :rtype:
161
+ # """
162
+ # jar_path = self._get_dstpath(plugin_id, layer)
163
+ # return jar_path if jar_path.is_file() else None
83
164
 
84
165
  def get_file_naming_convention(self) -> PluginRegistryLayoutFileNamingConvention:
166
+ """
167
+ Returns the concrete implementation's ``file_naming`convention`` field.
168
+
169
+ :return: This plugin registry layout's file naming convention.
170
+ :rtype: PluginRegistryLayoutFileNamingConvention
171
+ """
85
172
  return getattr(self, 'file_naming_convention')
86
173
 
87
174
  def get_plugin_registry(self) -> PluginRegistry:
175
+ """
176
+ Returns the enclosing plugin registry.
177
+
178
+ See ``initialize``.
179
+
180
+ :return: The enclosing plugin registry.
181
+ :rtype: PluginRegistry
182
+ :raises ValueError: If ``initialize`` was not called on the object.
183
+ """
88
184
  if self._plugin_registry is None:
89
185
  raise ValueError('Uninitialized plugin registry')
90
186
  return self._plugin_registry
91
187
 
92
188
  def get_type(self) -> PluginRegistryLayoutType:
189
+ """
190
+ Returns the concrete implementation's ``type`` field.
191
+
192
+ :return: This plugin registry layout's type.
193
+ :rtype: PluginRegistryLayoutType
194
+ """
93
195
  return getattr(self, 'type')
94
196
 
95
- def initialize(self, plugin_registry: PluginRegistry) -> BasePluginRegistryLayout:
197
+ def initialize(self,
198
+ plugin_registry: PluginRegistry) -> BasePluginRegistryLayout:
199
+ """
200
+ Initializes the plugin registry backreference. Mandatory call after
201
+ object creation.
202
+
203
+ :param plugin_registry: The enclosing plugin registry.
204
+ :type plugin_registry: PluginRegistry
205
+ :return: This object (for chaining).
206
+ :rtype: BasePluginRegistryLayout
207
+ """
96
208
  self._plugin_registry = plugin_registry
97
209
  return self
98
210
 
99
- def model_post_init(self, context: Any) -> None:
211
+ def model_post_init(self,
212
+ context: Any) -> None:
213
+ """
214
+ Pydantic post-initialization method, to create the ``_plugin_registry``
215
+ backreference.
216
+
217
+ See ``initialize``.
218
+
219
+ :param context: The Pydantic context.
220
+ :type context: Any
221
+ """
100
222
  super().model_post_init(context)
101
223
  self._plugin_registry = None
102
224
 
103
225
  @abstractmethod
104
- def _copy_jar(self, src_path: Path, dst_path: Path, interactive: bool=False) -> None:
226
+ def _copy_jar(self,
227
+ src_path: Path,
228
+ dst_path: Path) -> None:
229
+ """
230
+ Implementation-specific copy of the plugin JAR from a source path to its
231
+ intended deployed path.
232
+
233
+ :param src_path: The path of the plugin JAR to be deployed.
234
+ :type src_path: Path
235
+ :param dst_path: The intended path of the deployed JAR.
236
+ :type dst_path: Path
237
+ """
105
238
  pass
106
239
 
107
- def _get_dstfile(self, plugin_id: PluginIdentifier) -> str:
240
+ def _get_dstfile(self,
241
+ plugin_id: PluginIdentifier) -> str:
242
+ """
243
+ Computes the destination file name (not path) based on this layout's
244
+ file naming convention.
245
+
246
+ Implemented here because common to both concrete implementations.
247
+
248
+ :param plugin_id: A plugin identifier.
249
+ :type plugin_id: PluginIdentifier
250
+ :return: A file name string consistent with the file naming convention.
251
+ For a plugin identifier ``org.myproject.plugin.MyPlugin``, the
252
+ result is ``MyPlugin.jar`` for ``abbreviated``,
253
+ ``org.myproject.plugin.MyPlugin.jar`` for ``identifier`` and
254
+ ``org_myproject_plugin_MyPlugin.jar`` for ``underscore``.
255
+ :rtype: str
256
+ """
108
257
  if (conv := self.get_file_naming_convention()) == 'abbreviated':
109
258
  return f'{plugin_id.split(".")[-1]}.jar'
110
259
  elif conv == 'identifier':
@@ -114,10 +263,44 @@ class BasePluginRegistryLayout(BaseModel, ABC):
114
263
  else:
115
264
  raise InternalError()
116
265
 
117
- def _get_dstpath(self, plugin_id: PluginIdentifier, layer: PluginRegistryLayer) -> Path:
266
+ def _get_dstpath(self,
267
+ plugin_id: PluginIdentifier,
268
+ layer: PluginRegistryLayer) -> Path:
269
+ """
270
+ Computes the destination path for the given plugin being deployed to the
271
+ target layer, using the layout's file naming convention.
272
+
273
+ :param plugin_id: A plugin identifier.
274
+ :type plugin_id: PluginIdentifier
275
+ :param layer: A target plugin registry layer.
276
+ :type layer: PluginRegistryLayer
277
+ :return: The would-be destination of the deployed JAR.
278
+ :rtype: Path
279
+ """
118
280
  return layer.get_path().joinpath(self._get_dstfile(plugin_id))
119
281
 
120
- def _proceed_copy(self, src_path: Path, dst_path: Path, layer: PluginRegistryLayer, interactive: bool=False) -> bool:
282
+ def _proceed_copy(self,
283
+ src_path: Path,
284
+ dst_path: Path,
285
+ layer: PluginRegistryLayer,
286
+ interactive: bool=False) -> bool:
287
+ """
288
+ Determines whether the copy of the JAR should proceed.
289
+
290
+ :param src_path: The path of the JAR being deployed.
291
+ :type src_path: Path
292
+ :param dst_path: The path of the intended deployed JAR.
293
+ :type dst_path: Path
294
+ :param layer: The target plugin registry layer.
295
+ :type layer: PluginRegistryLayer
296
+ :param interactive: Whether interactive prompts are allowed (False by
297
+ default)
298
+ :type interactive: bool
299
+ :return: True, unless the destination file does not exist yet, the
300
+ interactive flag is True, and the user does not respond
301
+ positively to the confirmation prompt.
302
+ :rtype: bool
303
+ """
121
304
  if not dst_path.exists():
122
305
  if interactive:
123
306
  i = input(f'{dst_path} does not exist in {self.get_plugin_registry().get_id()}:{layer.get_id()} ({layer.get_name()}); create it (y/n)? [n] ').lower() or 'n'
@@ -127,28 +310,74 @@ class BasePluginRegistryLayout(BaseModel, ABC):
127
310
 
128
311
 
129
312
  class DirectoryPluginRegistryLayout(BasePluginRegistryLayout):
313
+ """
314
+ A plugin registry layout that keeps plugin JARs in a single directory.
315
+ """
316
+
317
+ #: This plugin registry layout's type.
130
318
  type: Literal['directory'] = Field(**BasePluginRegistryLayout.TYPE_FIELD)
131
- file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
132
319
 
133
- def _copy_jar(self, src_path: Path, dst_path: Path, interactive: bool=False) -> None:
134
- dst_dir, basename = dst_path.parent, dst_path.name
320
+ #: This plugin registry layout's file naming convention.
321
+ file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT,
322
+ **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
323
+
324
+ def _copy_jar(self,
325
+ src_path: Path,
326
+ dst_path: Path) -> None:
327
+ """
328
+ Copies the plugin JAR from a source path to its intended deployed path.
329
+
330
+ Additionally, if SELinux is enabled, sets the type of the security
331
+ context of the deployed path to ``httpd_sys_content_t``.
332
+
333
+ :param src_path: The path of the plugin JAR to be deployed.
334
+ :type src_path: Path
335
+ :param dst_path: The intended path of the deployed JAR.
336
+ :type dst_path: Path
337
+ :raises subprocess.CalledProcessError: If an invoked subprocess fails.
338
+ """
339
+ dst_dir, dst_file = dst_path.parent, dst_path.name
135
340
  subprocess.run(['cp', str(src_path), str(dst_path)], check=True, cwd=dst_dir)
136
341
  if subprocess.run('command -v selinuxenabled > /dev/null && selinuxenabled && command -v chcon > /dev/null', shell=True).returncode == 0:
137
- cmd = ['chcon', '-t', 'httpd_sys_content_t', basename]
342
+ cmd = ['chcon', '-t', 'httpd_sys_content_t', dst_file]
138
343
  subprocess.run(cmd, check=True, cwd=dst_dir)
139
344
 
140
345
 
141
346
  class RcsPluginRegistryLayout(DirectoryPluginRegistryLayout):
347
+ """
348
+ A plugin registry layout that is like ``DirectoryPluginRegistryLayout`` but
349
+ also uses `GNU RCS <https://www.gnu.org/software/rcs/>`_ to keep a record of
350
+ successive plugin versions in an ``RCS`` subdirectory.
351
+ """
352
+
353
+ #: This plugin registry layout's type. Shadows that of ``DirectoryPluginRegistryLayout`` due to inheritance.
142
354
  type: Literal['rcs'] = Field(**BasePluginRegistryLayout.TYPE_FIELD)
143
- file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
144
355
 
145
- def _copy_jar(self, src_path: Path, dst_path: Path, interactive: bool=False) -> None:
146
- dst_dir, basename = dst_path.parent, dst_path.name
356
+ # Believed to be unnecessary:
357
+
358
+ #file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
359
+
360
+ def _copy_jar(self,
361
+ src_path: Path,
362
+ dst_path: Path) -> None:
363
+ """
364
+ Copies the plugin JAR from a source path to its intended deployed path.
365
+
366
+ Does ``co -l`` if applicable, does the same copy as the parent
367
+ ``DirectoryPluginRegistryLayout._copy_jar``, then does ``ci -u``.
368
+
369
+ :param src_path: The path of the plugin JAR to be deployed.
370
+ :type src_path: Path
371
+ :param dst_path: The intended path of the deployed JAR.
372
+ :type dst_path: Path
373
+ :raises subprocess.CalledProcessError: If an invoked subprocess fails.
374
+ """
375
+ dst_dir, dst_file = dst_path.parent, dst_path.name
147
376
  plugin = Plugin.from_jar(src_path)
148
- rcs_path = dst_dir.joinpath('RCS', f'{basename},v')
377
+ rcs_path = dst_dir.joinpath('RCS', f'{dst_file},v')
149
378
  # Maybe do co -l before the parent's copy
150
379
  if dst_path.exists() and rcs_path.is_file():
151
- cmd = ['co', '-l', basename]
380
+ cmd = ['co', '-l', dst_file]
152
381
  subprocess.run(cmd, check=True, cwd=dst_dir)
153
382
  # Do the parent's copy
154
383
  super()._copy_jar(src_path, dst_path)
@@ -156,101 +385,296 @@ class RcsPluginRegistryLayout(DirectoryPluginRegistryLayout):
156
385
  cmd = ['ci', '-u', f'-mVersion {plugin.get_version()}']
157
386
  if not rcs_path.is_file():
158
387
  cmd.append(f'-t-{plugin.get_name()}')
159
- cmd.append(basename)
388
+ cmd.append(dst_file)
160
389
  subprocess.run(cmd, check=True, cwd=dst_dir)
161
390
 
162
391
 
392
+ #: A type alias for plugin registry layouts, which is the union of
393
+ #: ``DirectoryPluginRegistryLayout`` and ``RcsPluginRegistryLayout`` using
394
+ #: ``type`` as the discriminator field.
163
395
  PluginRegistryLayout = Annotated[Union[DirectoryPluginRegistryLayout, RcsPluginRegistryLayout], Field(discriminator='type')]
164
396
 
165
397
 
398
+ #: A type alias for plugin registry layer identifiers.
166
399
  PluginRegistryLayerIdentifier = str
167
400
 
168
401
 
169
- class PluginRegistryLayer(BaseModel, ABC):
170
- PRODUCTION: ClassVar[str] = 'production'
171
- TESTING: ClassVar[str] = 'testing'
402
+ class PluginRegistryLayer(BaseModel):
403
+ """
404
+ A Pydantic model to represent a plugin registry layer.
405
+ """
172
406
 
173
- id: PluginRegistryLayerIdentifier = Field(title='Plugin Registry Layer Identifier', description='An identifier for the plugin registry layer')
174
- name: str = Field(title='Plugin Registry Layer Name', description='A name for the plugin registry layer')
175
- path: str = Field(title='Plugin Registry Layer Path', description='A root path for the plugin registry layer')
407
+ #: This plugin registry layer's identifier.
408
+ id: PluginRegistryLayerIdentifier = Field(title='Plugin Registry Layer Identifier',
409
+ description='An identifier for the plugin registry layer')
176
410
 
411
+ #: This plugin registry layer's name.
412
+ name: str = Field(title='Plugin Registry Layer Name',
413
+ description='A name for the plugin registry layer')
414
+
415
+ #: This plugin registry layer's path.
416
+ path: str = Field(title='Plugin Registry Layer Path',
417
+ description='A root path for the plugin registry layer')
418
+
419
+ #: Internal backreference to the enclosing plugin registry; see ``initialize``.
177
420
  _plugin_registry: Optional[PluginRegistry]
178
421
 
179
- def deploy_plugin(self, plugin_id: PluginIdentifier, src_path: Path, interactive: bool=False) -> Optional[tuple[Path, Plugin]]:
422
+ def deploy_plugin(self,
423
+ plugin_id: PluginIdentifier,
424
+ src_path: Path,
425
+ interactive: bool=False) -> Optional[tuple[Path, Plugin]]:
426
+ """
427
+ Deploys the given plugin to this plugin registry layer according to
428
+ this plugin registry layout's file naming convention.
429
+
430
+ :param plugin_id: A plugin identifier.
431
+ :type plugin_id: PluginIdentifier
432
+ :param src_path: The path of the plugin JAR.
433
+ :type src_path: Path
434
+ :param interactive: If False (the default), no interactive confirmation
435
+ will occur. If True, and the given plugin is being
436
+ deployed to this layer for the very first time, the
437
+ user will be prompted interactively to confirm.
438
+ :type interactive: bool
439
+ :return: A tuple of the path of the deployed JAR and a Plugin object
440
+ instantiated from the source JAR, or None if the user was
441
+ prompted for confirmation and responded negatively.
442
+ :rtype: Optional[tuple[Path, Plugin]]
443
+ """
180
444
  return self.get_plugin_registry().get_layout().deploy_plugin(plugin_id, self, src_path, interactive)
181
445
 
182
446
  def get_id(self) -> PluginRegistryLayerIdentifier:
447
+ """
448
+ Returns this plugin registry layer's identifier.
449
+
450
+ :return: This plugin registry layer's identifier.
451
+ :rtype: PluginRegistryLayerIdentifier
452
+ """
183
453
  return self.id
184
454
 
185
455
  def get_jars(self) -> list[Path]:
456
+ """
457
+ Returns the list of this plugin registry layer's JAR file paths.
458
+
459
+ :return: A sorted list of JAR file paths.
460
+ :rtype: list[Path]
461
+ """
462
+ # FIXME Strictly speaking this should be in the layout
186
463
  return sorted(self.get_path().glob('*.jar'))
187
464
 
188
465
  def get_name(self) -> str:
466
+ """
467
+ Returns this plugin registry layer's name.
468
+
469
+ :return: This plugin registry layer's name.
470
+ :rtype: str
471
+ """
189
472
  return self.name
190
473
 
191
474
  def get_path(self) -> Path:
475
+ """
476
+ Returns this plugin registry layer's path.
477
+
478
+ :return: This plugin registry layer's path.
479
+ :rtype: Path
480
+ """
192
481
  return path(self.path)
193
482
 
194
483
  def get_plugin_registry(self) -> PluginRegistry:
484
+ """
485
+ Returns the enclosing plugin registry.
486
+
487
+ See ``initialize``.
488
+
489
+ :return: The enclosing plugin registry.
490
+ :rtype: PluginRegistry
491
+ :raises ValueError: If ``initialize`` was not called on the object.
492
+ """
195
493
  if self._plugin_registry is None:
196
494
  raise ValueError('Uninitialized plugin registry')
197
495
  return self._plugin_registry
198
496
 
199
- def initialize(self, plugin_registry: PluginRegistry) -> PluginRegistryLayer:
497
+ def initialize(self,
498
+ plugin_registry: PluginRegistry) -> PluginRegistryLayer:
499
+ """
500
+ Initializes the plugin registry backreference. Mandatory call after
501
+ object creation.
502
+
503
+ :param plugin_registry: The enclosing plugin registry.
504
+ :type plugin_registry: PluginRegistry
505
+ :return: This object (for chaining).
506
+ :rtype: BasePluginRegistryLayout
507
+ """
200
508
  self._plugin_registry = plugin_registry
201
509
  return self
202
510
 
203
- def model_post_init(self, context: Any) -> None:
511
+ def model_post_init(self,
512
+ context: Any) -> None:
513
+ """
514
+ Pydantic post-initialization method, to create the ``_plugin_registry``
515
+ backreference.
516
+
517
+ See ``initialize``.
518
+
519
+ :param context: The Pydantic context.
520
+ :type context: Any
521
+ """
204
522
  super().model_post_init(context)
205
523
  self._plugin_registry = None
206
524
 
207
525
 
526
+ #: A type alias for the plugin registry kind.
208
527
  PluginRegistryKind = Literal['PluginRegistry']
209
528
 
210
529
 
530
+ #: A type alias for plugin registry identifiers.
211
531
  PluginRegistryIdentifier = str
212
532
 
213
533
 
214
534
  class PluginRegistry(BaseModelWithRoot):
215
- kind: PluginRegistryKind = Field(title='Kind', description="This object's kind")
216
- id: PluginRegistryIdentifier = Field(title='Plugin Registry Identifier', description='An identifier for the plugin set')
217
- name: str = Field(title='Plugin Registry Name', description='A name for the plugin set')
218
- layout: PluginRegistryLayout = Field(title='Plugin Registry Layout', description='A layout for the plugin registry')
219
- layers: list[PluginRegistryLayer] = Field(min_length=1, title='Plugin Registry Layers', description="A non-empty list of plugin registry layers")
220
- plugin_identifiers: list[PluginIdentifier] = Field(alias='plugin-identifiers', min_length=1, title='Plugin Identifiers', description="A non-empty list of plugin identifiers")
221
- suppressed_plugin_identifiers: list[PluginIdentifier] = Field([], alias='suppressed-plugin-identifiers', title='Suppressed Plugin Identifiers', description="A list of suppressed plugin identifiers")
535
+ """
536
+ A Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to represent a
537
+ plugin registry.
538
+ """
539
+
540
+ #: This object's kind.
541
+ kind: PluginRegistryKind = Field(title='Kind',
542
+ description="This object's kind")
543
+
544
+ #: This plugin registry's identifier.
545
+ id: PluginRegistryIdentifier = Field(title='Plugin Registry Identifier',
546
+ description='An identifier for the plugin set')
547
+
548
+ #: This plugin registry's name.
549
+ name: str = Field(title='Plugin Registry Name',
550
+ description='A name for the plugin set')
551
+
552
+ #: This plugin registry's layout.
553
+ layout: PluginRegistryLayout = Field(title='Plugin Registry Layout',
554
+ description='A layout for the plugin registry')
555
+
556
+ #: This plugin registry's layers.
557
+ layers: list[PluginRegistryLayer] = Field(min_length=1,
558
+ title='Plugin Registry Layers',
559
+ description="A non-empty list of plugin registry layers")
560
+
561
+ #: The plugin identifiers in this registry.
562
+ plugin_identifiers: list[PluginIdentifier] = Field(alias='plugin-identifiers',
563
+ min_length=1,
564
+ title='Plugin Identifiers',
565
+ description="A non-empty list of plugin identifiers")
566
+
567
+ #: The suppressed plugin identifiers, formerly in this plugin registry.
568
+ suppressed_plugin_identifiers: list[PluginIdentifier] = Field([],
569
+ alias='suppressed-plugin-identifiers',
570
+ title='Suppressed Plugin Identifiers',
571
+ description="A list of suppressed plugin identifiers")
222
572
 
223
573
  def get_id(self) -> PluginRegistryIdentifier:
574
+ """
575
+ Returns this plugin registry's identifier.
576
+
577
+ :return: This plugin registry's identifier.
578
+ :rtype: PluginRegistryIdentifier
579
+ """
224
580
  return self.id
225
581
 
226
- def get_layer(self, layer_id: PluginRegistryLayerIdentifier) -> Optional[PluginRegistryLayer]:
582
+ def get_layer(self,
583
+ layer_id: PluginRegistryLayerIdentifier) -> Optional[PluginRegistryLayer]:
584
+ """
585
+ Returns the plugin registry layer with the given identifier.
586
+
587
+ :param layer_id: A plugin registry layer identifier.
588
+ :type layer_id: PluginRegistryLayerIdentifier
589
+ :return: The plugin registry layer from this registry with the given
590
+ identifier, or None if there is no such layer.
591
+ :rtype: Optional[PluginRegistryLayer]
592
+ """
227
593
  for layer in self.get_layers():
228
594
  if layer.get_id() == layer_id:
229
595
  return layer
230
596
  return None
231
597
 
232
598
  def get_layer_ids(self) -> list[PluginRegistryLayerIdentifier]:
599
+ """
600
+ Returns a list of all the plugin registry layer identifiers in this
601
+ registry.
602
+
603
+ :return: A list of plugin registry layer identifiers.
604
+ :rtype: list[PluginRegistryLayerIdentifier]
605
+ """
233
606
  return [layer.get_id() for layer in self.get_layers()]
234
607
 
235
608
  def get_layers(self) -> list[PluginRegistryLayer]:
609
+ """
610
+ Returns a list of all the plugin registry layers in this registry.
611
+
612
+ :return: A list of plugin registry layers.
613
+ :rtype: list[PluginRegistryLayer]
614
+ """
236
615
  return self.layers
237
616
 
238
617
  def get_layout(self) -> BasePluginRegistryLayout:
618
+ """
619
+ Returns this plugin registry's layout.
620
+
621
+ :return: A list of plugin registry layers.
622
+ :rtype: list[PluginRegistryLayer]
623
+ """
239
624
  return self.layout
240
625
 
241
626
  def get_name(self) -> str:
627
+ """
628
+ Returns this plugin registry's name.
629
+
630
+ :return: This plugin registry's name.
631
+ :rtype: str
632
+ """
242
633
  return self.name
243
634
 
244
635
  def get_plugin_identifiers(self) -> list[PluginIdentifier]:
636
+ """
637
+ Returns the list of plugin identifiers in this registry.
638
+
639
+ :return: The list of plugin identifiers in this registry.
640
+ :rtype: list[PluginIdentifier]
641
+ """
245
642
  return self.plugin_identifiers
246
643
 
247
644
  def get_suppressed_plugin_identifiers(self) -> list[PluginIdentifier]:
645
+ """
646
+ Returns the list of suppressed plugin identifiers in this registry.
647
+
648
+ :return: The list of suppressed plugin identifiers in this registry.
649
+ :rtype: list[PluginIdentifier]
650
+ """
248
651
  return self.suppressed_plugin_identifiers
249
652
 
250
- def has_plugin(self, plugin_id: PluginIdentifier) -> bool:
653
+ def has_plugin(self,
654
+ plugin_id: PluginIdentifier) -> bool:
655
+ """
656
+ Determines if a given plugin identifier is in this registry.
657
+
658
+ :param plugin_id: A plugin identifier.
659
+ :type plugin_id: PluginIdentifier
660
+ :return: True if and only if the given plugin identifier is in this
661
+ registry.
662
+ :rtype: bool
663
+ """
251
664
  return plugin_id in self.get_plugin_identifiers()
252
665
 
253
- def model_post_init(self, context: Any) -> None:
666
+ def model_post_init(self,
667
+ context: Any) -> None:
668
+ """
669
+ Pydantic post-initialization method to initialize the layout and all the
670
+ layers with this registry as the enclosing registry.
671
+
672
+ See ``BasePluginRegistrLayout.initialize`` and
673
+ ``PluginRegistryLayout.initialize``.
674
+
675
+ :param context: The Pydantic context.
676
+ :type context: Any
677
+ """
254
678
  super().model_post_init(context)
255
679
  self.get_layout().initialize(self)
256
680
  for layer in self.get_layers():