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.
@@ -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,67 +49,211 @@ from .plugin import Plugin, PluginIdentifier
45
49
  from .util import BaseModelWithRoot
46
50
 
47
51
 
48
-
52
+ #: A type alias for the plugin registry catalog kind.
49
53
  PluginRegistryCatalogKind = Literal['PluginRegistryCatalog']
50
54
 
51
55
 
52
56
  class PluginRegistryCatalog(BaseModelWithRoot):
53
- kind: PluginRegistryCatalogKind = Field(description="This object's kind")
54
- plugin_registry_files: list[str] = Field(min_length=1, description="A non-empty list of plugin registry files", title='Plugin Registry Files', alias='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")
55
71
 
56
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
+ """
57
80
  return [self.get_root().joinpath(pstr) for pstr in self.plugin_registry_files]
58
81
 
59
82
 
83
+ #: A type alias for the two plugin registry layout types.
60
84
  PluginRegistryLayoutType = Literal['directory', 'rcs']
61
85
 
62
86
 
87
+ #: A type alias for the three plugin registry layout file naming conventions.
63
88
  PluginRegistryLayoutFileNamingConvention = Literal['abbreviated', 'identifier', 'underscore']
64
89
 
65
90
 
66
91
  class BasePluginRegistryLayout(BaseModel, ABC):
67
- 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.
68
103
  FILE_NAMING_CONVENTION_DEFAULT: ClassVar[PluginRegistryLayoutFileNamingConvention] = 'identifier'
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
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``.
71
111
  _plugin_registry: Optional[PluginRegistry]
72
112
 
73
- 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
+ """
74
141
  src_path = path(src_path) # in case it's a string
75
142
  dst_path = self._get_dstpath(plugin_id, layer)
76
143
  if not self._proceed_copy(src_path, dst_path, layer, interactive=interactive):
77
144
  return None
78
- self._copy_jar(src_path, dst_path, interactive=interactive)
145
+ self._copy_jar(src_path, dst_path)
79
146
  return dst_path, Plugin.from_jar(src_path)
80
147
 
81
- def get_file_for(self, plugin_id, layer: PluginRegistryLayer) -> Optional[Path]:
82
- jar_path = self._get_dstpath(plugin_id, layer)
83
- 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
84
164
 
85
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
+ """
86
172
  return getattr(self, 'file_naming_convention')
87
173
 
88
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
+ """
89
184
  if self._plugin_registry is None:
90
185
  raise ValueError('Uninitialized plugin registry')
91
186
  return self._plugin_registry
92
187
 
93
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
+ """
94
195
  return getattr(self, 'type')
95
196
 
96
- 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
+ """
97
208
  self._plugin_registry = plugin_registry
98
209
  return self
99
210
 
100
- 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
+ """
101
222
  super().model_post_init(context)
102
223
  self._plugin_registry = None
103
224
 
104
225
  @abstractmethod
105
- 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
+ """
106
238
  pass
107
239
 
108
- 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
+ """
109
257
  if (conv := self.get_file_naming_convention()) == 'abbreviated':
110
258
  return f'{plugin_id.split(".")[-1]}.jar'
111
259
  elif conv == 'identifier':
@@ -115,10 +263,44 @@ class BasePluginRegistryLayout(BaseModel, ABC):
115
263
  else:
116
264
  raise InternalError()
117
265
 
118
- 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
+ """
119
280
  return layer.get_path().joinpath(self._get_dstfile(plugin_id))
120
281
 
121
- 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
+ """
122
304
  if not dst_path.exists():
123
305
  if interactive:
124
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'
@@ -128,28 +310,74 @@ class BasePluginRegistryLayout(BaseModel, ABC):
128
310
 
129
311
 
130
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.
131
318
  type: Literal['directory'] = Field(**BasePluginRegistryLayout.TYPE_FIELD)
132
- file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
133
319
 
134
- def _copy_jar(self, src_path: Path, dst_path: Path, interactive: bool=False) -> None:
135
- 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
136
340
  subprocess.run(['cp', str(src_path), str(dst_path)], check=True, cwd=dst_dir)
137
341
  if subprocess.run('command -v selinuxenabled > /dev/null && selinuxenabled && command -v chcon > /dev/null', shell=True).returncode == 0:
138
- cmd = ['chcon', '-t', 'httpd_sys_content_t', basename]
342
+ cmd = ['chcon', '-t', 'httpd_sys_content_t', dst_file]
139
343
  subprocess.run(cmd, check=True, cwd=dst_dir)
140
344
 
141
345
 
142
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.
143
354
  type: Literal['rcs'] = Field(**BasePluginRegistryLayout.TYPE_FIELD)
144
- file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
145
355
 
146
- def _copy_jar(self, src_path: Path, dst_path: Path, interactive: bool=False) -> None:
147
- 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
148
376
  plugin = Plugin.from_jar(src_path)
149
- rcs_path = dst_dir.joinpath('RCS', f'{basename},v')
377
+ rcs_path = dst_dir.joinpath('RCS', f'{dst_file},v')
150
378
  # Maybe do co -l before the parent's copy
151
379
  if dst_path.exists() and rcs_path.is_file():
152
- cmd = ['co', '-l', basename]
380
+ cmd = ['co', '-l', dst_file]
153
381
  subprocess.run(cmd, check=True, cwd=dst_dir)
154
382
  # Do the parent's copy
155
383
  super()._copy_jar(src_path, dst_path)
@@ -157,101 +385,296 @@ class RcsPluginRegistryLayout(DirectoryPluginRegistryLayout):
157
385
  cmd = ['ci', '-u', f'-mVersion {plugin.get_version()}']
158
386
  if not rcs_path.is_file():
159
387
  cmd.append(f'-t-{plugin.get_name()}')
160
- cmd.append(basename)
388
+ cmd.append(dst_file)
161
389
  subprocess.run(cmd, check=True, cwd=dst_dir)
162
390
 
163
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.
164
395
  PluginRegistryLayout = Annotated[Union[DirectoryPluginRegistryLayout, RcsPluginRegistryLayout], Field(discriminator='type')]
165
396
 
166
397
 
398
+ #: A type alias for plugin registry layer identifiers.
167
399
  PluginRegistryLayerIdentifier = str
168
400
 
169
401
 
170
- class PluginRegistryLayer(BaseModel, ABC):
171
- PRODUCTION: ClassVar[str] = 'production'
172
- TESTING: ClassVar[str] = 'testing'
402
+ class PluginRegistryLayer(BaseModel):
403
+ """
404
+ A Pydantic model to represent a plugin registry layer.
405
+ """
173
406
 
174
- id: PluginRegistryLayerIdentifier = Field(title='Plugin Registry Layer Identifier', description='An identifier for the plugin registry layer')
175
- name: str = Field(title='Plugin Registry Layer Name', description='A name for the plugin registry layer')
176
- 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')
177
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``.
178
420
  _plugin_registry: Optional[PluginRegistry]
179
421
 
180
- 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
+ """
181
444
  return self.get_plugin_registry().get_layout().deploy_plugin(plugin_id, self, src_path, interactive)
182
445
 
183
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
+ """
184
453
  return self.id
185
454
 
186
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
187
463
  return sorted(self.get_path().glob('*.jar'))
188
464
 
189
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
+ """
190
472
  return self.name
191
473
 
192
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
+ """
193
481
  return path(self.path)
194
482
 
195
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
+ """
196
493
  if self._plugin_registry is None:
197
494
  raise ValueError('Uninitialized plugin registry')
198
495
  return self._plugin_registry
199
496
 
200
- 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
+ """
201
508
  self._plugin_registry = plugin_registry
202
509
  return self
203
510
 
204
- 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
+ """
205
522
  super().model_post_init(context)
206
523
  self._plugin_registry = None
207
524
 
208
525
 
526
+ #: A type alias for the plugin registry kind.
209
527
  PluginRegistryKind = Literal['PluginRegistry']
210
528
 
211
529
 
530
+ #: A type alias for plugin registry identifiers.
212
531
  PluginRegistryIdentifier = str
213
532
 
214
533
 
215
534
  class PluginRegistry(BaseModelWithRoot):
216
- kind: PluginRegistryKind = Field(title='Kind', description="This object's kind")
217
- id: PluginRegistryIdentifier = Field(title='Plugin Registry Identifier', description='An identifier for the plugin set')
218
- name: str = Field(title='Plugin Registry Name', description='A name for the plugin set')
219
- layout: PluginRegistryLayout = Field(title='Plugin Registry Layout', description='A layout for the plugin registry')
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(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")
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")
223
572
 
224
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
+ """
225
580
  return self.id
226
581
 
227
- 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
+ """
228
593
  for layer in self.get_layers():
229
594
  if layer.get_id() == layer_id:
230
595
  return layer
231
596
  return None
232
597
 
233
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
+ """
234
606
  return [layer.get_id() for layer in self.get_layers()]
235
607
 
236
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
+ """
237
615
  return self.layers
238
616
 
239
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
+ """
240
624
  return self.layout
241
625
 
242
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
+ """
243
633
  return self.name
244
634
 
245
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
+ """
246
642
  return self.plugin_identifiers
247
643
 
248
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
+ """
249
651
  return self.suppressed_plugin_identifiers
250
652
 
251
- 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
+ """
252
664
  return plugin_id in self.get_plugin_identifiers()
253
665
 
254
- 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
+ """
255
678
  super().model_post_init(context)
256
679
  self.get_layout().initialize(self)
257
680
  for layer in self.get_layers():