lockss-turtles 0.6.0.dev2__py3-none-any.whl → 0.6.0.dev4__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.
@@ -33,102 +33,78 @@
33
33
  from __future__ import annotations
34
34
 
35
35
  from abc import ABC, abstractmethod
36
- import importlib.resources as IR
37
36
  import os
38
- from pathlib import Path, PurePath
37
+ from pathlib import Path
39
38
  import shlex
40
39
  import subprocess
41
40
  import sys
42
- from typing import List, Tuple, Union
41
+ from typing import Annotated, Any, ClassVar, Literal, Optional, Union
43
42
 
44
- from lockss.pybasic.fileutil import path
43
+ from pydantic import BaseModel, Field
45
44
 
46
- from . import resources as __resources__
47
- from .plugin import Plugin
48
- from .util import YamlT, load_and_validate
45
+ from .plugin import Plugin, PluginIdentifier
46
+ from .util import BaseModelWithRoot
49
47
 
50
48
 
51
- class PluginSetCatalog(object):
49
+ PluginSetCatalogKind = Literal['PluginSetCatalog']
52
50
 
53
- PLUGIN_SET_CATALOG_SCHEMA = 'plugin-set-catalog-schema.json'
54
51
 
55
- def __init__(self, parsed: YamlT) -> None:
56
- super().__init__()
57
- self._parsed: YamlT = parsed
52
+ class PluginSetCatalog(BaseModelWithRoot):
53
+ kind: PluginSetCatalogKind = Field(description="This object's kind")
54
+ 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')
58
55
 
59
- def get_plugin_set_files(self) -> List[str]:
60
- return self._parsed['plugin-set-files']
56
+ def get_plugin_set_files(self) -> list[Path]:
57
+ return [self.get_root().joinpath(p) for p in self.plugin_set_files]
61
58
 
62
- @staticmethod
63
- def from_path(plugin_set_catalog_path: Union[PurePath, str]) -> PluginSetCatalog:
64
- plugin_set_catalog_path = path(plugin_set_catalog_path)
65
- with IR.path(__resources__, PluginSetCatalog.PLUGIN_SET_CATALOG_SCHEMA) as plugin_set_catalog_schema_path:
66
- parsed = load_and_validate(plugin_set_catalog_schema_path, plugin_set_catalog_path)
67
- return PluginSetCatalog(parsed)
68
59
 
60
+ PluginSetBuilderType = Literal['ant', 'maven']
69
61
 
70
- class PluginSet(ABC):
71
62
 
72
- PLUGIN_SET_SCHEMA = 'plugin-set-schema.json'
73
-
74
- def __init__(self, parsed: YamlT) -> None:
75
- super().__init__()
76
- self._parsed: YamlT = parsed
63
+ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
64
+ TYPE_FIELD: ClassVar[dict[str, str]] = dict(description='A plugin builder type', title='Plugin Builder Type')
65
+ MAIN_FIELD: ClassVar[dict[str, str]] = dict(description="The path to the plugins' source code, relative to the root of the project", title='Main Code Path')
66
+ TEST_FIELD: ClassVar[dict[str, str]] = dict(description="The path to the plugins' unit tests, relative to the root of the project", title='Test Code Path')
77
67
 
78
68
  @abstractmethod
79
- def build_plugin(self, plugin_id, keystore_path, keystore_alias, keystore_password=None) -> Tuple[Path, Plugin]:
69
+ def build_plugin(self, plugin_id: PluginIdentifier, keystore_path: Path, keystore_alias: str, keystore_password=None) -> tuple[Path, Plugin]:
80
70
  pass
81
71
 
82
- def get_builder_type(self) -> str:
83
- return self._parsed['builder']['type']
84
-
85
- def get_id(self) -> str:
86
- return self._parsed['id']
72
+ def get_main(self) -> Path:
73
+ return self.get_root().joinpath(self._get_main())
87
74
 
88
- def get_name(self) -> str:
89
- return self._parsed['name']
75
+ def get_test(self) -> Path:
76
+ return self.get_root().joinpath(self._get_test())
90
77
 
91
- @abstractmethod
92
- def has_plugin(self, plugin_id: str) -> bool:
93
- pass
78
+ def get_type(self) -> PluginSetBuilderType:
79
+ return getattr(self, 'type')
94
80
 
95
- @abstractmethod
96
- def make_plugin(self, plugin_id: str) -> Plugin:
97
- pass
81
+ def has_plugin(self, plugin_id: PluginIdentifier) -> bool:
82
+ return self._plugin_path(plugin_id).is_file()
98
83
 
99
- @staticmethod
100
- def from_path(plugin_set_file_path) -> List[PluginSet]:
101
- plugin_set_file_path = path(plugin_set_file_path)
102
- with IR.path(__resources__, PluginSet.PLUGIN_SET_SCHEMA) as plugin_set_schema_path:
103
- lst = load_and_validate(plugin_set_schema_path, plugin_set_file_path, multiple=True)
104
- return [PluginSet._from_obj(parsed, plugin_set_file_path) for parsed in lst]
84
+ def make_plugin(self, plugin_id: PluginIdentifier) -> Plugin:
85
+ return Plugin.from_path(self._plugin_path(plugin_id))
105
86
 
106
- @staticmethod
107
- def _from_obj(parsed: YamlT, plugin_set_file_path: Path) -> PluginSet:
108
- typ = parsed['builder']['type']
109
- if typ == AntPluginSet.TYPE:
110
- return AntPluginSet(parsed, plugin_set_file_path)
111
- elif typ == MavenPluginSet.TYPE:
112
- return MavenPluginSet(parsed, plugin_set_file_path)
113
- else:
114
- raise Exception(f'{plugin_set_file_path!s}: unknown builder type: {typ}')
87
+ def _get_main(self) -> str:
88
+ return getattr(self, 'main')
115
89
 
90
+ def _get_test(self) -> str:
91
+ return getattr(self, 'test')
116
92
 
117
- class AntPluginSet(PluginSet):
93
+ def _plugin_path(self, plugin_id: PluginIdentifier) -> Path:
94
+ return self.get_main_path().joinpath(Plugin.id_to_file(plugin_id))
118
95
 
119
- TYPE = 'ant'
120
96
 
121
- DEFAULT_MAIN = 'plugins/src'
97
+ class AntPluginSetBuilder(BasePluginSetBuilder):
98
+ DEFAULT_MAIN: ClassVar[str] = 'plugins/src'
99
+ DEFAULT_TEST: ClassVar[str] = 'plugins/test/src'
122
100
 
123
- DEFAULT_TEST = 'plugins/test/src'
101
+ type: Literal['ant'] = Field(**BasePluginSetBuilder.TYPE_FIELD)
102
+ main: Optional[str] = Field(DEFAULT_MAIN, **BasePluginSetBuilder.MAIN_FIELD)
103
+ test: Optional[str] = Field(DEFAULT_TEST, **BasePluginSetBuilder.TEST_FIELD)
124
104
 
125
- def __init__(self, parsed: YamlT, fpath: Path) -> None:
126
- super().__init__(parsed)
127
- self._built: bool = False
128
- self._root: Path = fpath.parent
105
+ _built: bool
129
106
 
130
- # Returns (jar_path, plugin)
131
- def build_plugin(self, plugin_id, keystore_path, keystore_alias, keystore_password=None) -> Tuple[Path, Plugin]:
107
+ def build_plugin(self, plugin_id: PluginIdentifier, keystore_path: Path, keystore_alias: str, keystore_password=None) -> tuple[Path, Plugin]:
132
108
  # Prerequisites
133
109
  if 'JAVA_HOME' not in os.environ:
134
110
  raise Exception('error: JAVA_HOME must be set in your environment')
@@ -137,39 +113,18 @@ class AntPluginSet(PluginSet):
137
113
  # Little build
138
114
  return self._little_build(plugin_id, keystore_path, keystore_alias, keystore_password=keystore_password)
139
115
 
140
- def get_main(self) -> str:
141
- return self._parsed.get('main', AntPluginSet.DEFAULT_MAIN)
142
-
143
- def get_main_path(self) -> Path:
144
- return self.get_root_path().joinpath(self.get_main())
145
-
146
- def get_root(self) -> Path:
147
- return self._root
148
-
149
- def get_root_path(self) -> Path:
150
- return self.get_root().expanduser().resolve()
151
-
152
- def get_test(self) -> str:
153
- return self._parsed.get('test', AntPluginSet.DEFAULT_TEST)
154
-
155
- def get_test_path(self) -> Path:
156
- return self.get_root_path().joinpath(self.get_test())
157
-
158
- def has_plugin(self, plugin_id: str) -> bool:
159
- return self._plugin_path(plugin_id).is_file()
160
-
161
- def make_plugin(self, plugin_id: str) -> Plugin:
162
- return Plugin.from_path(self._plugin_path(plugin_id))
116
+ def model_post_init(self, context: Any) -> None:
117
+ super().model_post_init(context)
118
+ self._built = False
163
119
 
164
120
  def _big_build(self) -> None:
165
121
  if not self._built:
166
122
  # Do build
167
123
  subprocess.run('ant load-plugins',
168
- shell=True, cwd=self.get_root_path(), check=True, stdout=sys.stdout, stderr=sys.stderr)
124
+ shell=True, cwd=self.get_root(), check=True, stdout=sys.stdout, stderr=sys.stderr)
169
125
  self._built = True
170
126
 
171
- # Returns (jar_path, plugin)
172
- def _little_build(self, plugin_id: str, keystore_path: Path, keystore_alias: str, keystore_password: str=None) -> Tuple[Path, Plugin]:
127
+ def _little_build(self, plugin_id: PluginIdentifier, keystore_path: Path, keystore_alias: str, keystore_password: str=None) -> tuple[Path, Plugin]:
173
128
  orig_plugin = None
174
129
  cur_id = plugin_id
175
130
  # Get all directories for jarplugin -d
@@ -187,14 +142,14 @@ class AntPluginSet(PluginSet):
187
142
  cur_id = cur_plugin.get_parent_identifier()
188
143
  # Invoke jarplugin
189
144
  jar_fstr = Plugin.id_to_file(plugin_id)
190
- jar_path = self.get_root_path().joinpath('plugins/jars', f'{plugin_id}.jar')
145
+ jar_path = self.get_root().joinpath('plugins/jars', f'{plugin_id}.jar')
191
146
  jar_path.parent.mkdir(parents=True, exist_ok=True)
192
147
  cmd = ['test/scripts/jarplugin',
193
148
  '-j', str(jar_path),
194
149
  '-p', str(jar_fstr)]
195
150
  for d in dirs:
196
151
  cmd.extend(['-d', d])
197
- subprocess.run(cmd, cwd=self.get_root_path(), check=True, stdout=sys.stdout, stderr=sys.stderr)
152
+ subprocess.run(cmd, cwd=self.get_root(), check=True, stdout=sys.stdout, stderr=sys.stderr)
198
153
  # Invoke signplugin
199
154
  cmd = ['test/scripts/signplugin',
200
155
  '--jar', str(jar_path),
@@ -203,14 +158,14 @@ class AntPluginSet(PluginSet):
203
158
  if keystore_password is not None:
204
159
  cmd.extend(['--password', keystore_password])
205
160
  try:
206
- subprocess.run(cmd, cwd=self.get_root_path(), check=True, stdout=sys.stdout, stderr=sys.stderr)
161
+ subprocess.run(cmd, cwd=self.get_root(), check=True, stdout=sys.stdout, stderr=sys.stderr)
207
162
  except subprocess.CalledProcessError as cpe:
208
163
  raise self._sanitize(cpe)
209
164
  if not jar_path.is_file():
210
165
  raise FileNotFoundError(str(jar_path))
211
166
  return jar_path, orig_plugin
212
167
 
213
- def _plugin_path(self, plugin_id: str) -> Path:
168
+ def _plugin_path(self, plugin_id: PluginIdentifier) -> Path:
214
169
  return self.get_main_path().joinpath(Plugin.id_to_file(plugin_id))
215
170
 
216
171
  def _sanitize(self, called_process_error: subprocess.CalledProcessError) -> subprocess.CalledProcessError:
@@ -222,47 +177,23 @@ class AntPluginSet(PluginSet):
222
177
  return called_process_error
223
178
 
224
179
 
225
- class MavenPluginSet(PluginSet):
226
-
227
- TYPE = 'maven'
228
-
229
- DEFAULT_MAIN = 'src/main/java'
180
+ class MavenPluginSetBuilder(BasePluginSetBuilder):
181
+ DEFAULT_MAIN: ClassVar[str] = 'src/main/java'
182
+ DEFAULT_TEST: ClassVar[str] = 'src/test/java'
230
183
 
231
- DEFAULT_TEST = 'src/test/java'
184
+ type: Literal['maven'] = Field(**BasePluginSetBuilder.TYPE_FIELD)
185
+ main: Optional[str] = Field(DEFAULT_MAIN, **BasePluginSetBuilder.MAIN_FIELD)
186
+ test: Optional[str] = Field(DEFAULT_TEST, **BasePluginSetBuilder.TEST_FIELD)
232
187
 
233
- def __init__(self, parsed: YamlT, root: Path) -> None:
234
- super().__init__(parsed)
235
- self._built: bool = False
236
- self._root: Path = root.parent
188
+ _built: bool
237
189
 
238
- # Returns (jar_path, plugin)
239
- def build_plugin(self, plugin_id: str, keystore_path: Path, keystore_alias: str, keystore_password=None) -> Tuple[Path, Plugin]:
190
+ def build_plugin(self, plugin_id: PluginIdentifier, keystore_path: Path, keystore_alias: str, keystore_password=None) -> tuple[Path, Plugin]:
240
191
  self._big_build(keystore_path, keystore_alias, keystore_password=keystore_password)
241
192
  return self._little_build(plugin_id)
242
193
 
243
- def get_main(self) -> str:
244
- return self._parsed.get('main', MavenPluginSet.DEFAULT_MAIN)
245
-
246
- def get_main_path(self) -> Path:
247
- return self.get_root_path().joinpath(self.get_main())
248
-
249
- def get_root(self) -> Path:
250
- return self._root
251
-
252
- def get_root_path(self) -> Path:
253
- return self.get_root().expanduser().resolve()
254
-
255
- def get_test(self) -> str:
256
- return self._parsed.get('test', MavenPluginSet.DEFAULT_TEST)
257
-
258
- def get_test_path(self) -> Path:
259
- return self.get_root_path().joinpath(self.get_test())
260
-
261
- def has_plugin(self, plugin_id) -> bool:
262
- return self._plugin_path(plugin_id).is_file()
263
-
264
- def make_plugin(self, plugin_id) -> Plugin:
265
- return Plugin.from_path(self._plugin_path(plugin_id))
194
+ def model_post_init(self, context: Any) -> None:
195
+ super().model_post_init(context)
196
+ self._built = False
266
197
 
267
198
  def _big_build(self, keystore_path: Path, keystore_alias: str, keystore_password: str=None) -> None:
268
199
  if not self._built:
@@ -272,21 +203,17 @@ class MavenPluginSet(PluginSet):
272
203
  f'-Dkeystore.alias={keystore_alias}',
273
204
  f'-Dkeystore.password={keystore_password}']
274
205
  try:
275
- subprocess.run(cmd, cwd=self.get_root_path(), check=True, stdout=sys.stdout, stderr=sys.stderr)
206
+ subprocess.run(cmd, cwd=self.get_root(), check=True, stdout=sys.stdout, stderr=sys.stderr)
276
207
  except subprocess.CalledProcessError as cpe:
277
208
  raise self._sanitize(cpe)
278
209
  self._built = True
279
210
 
280
- # Returns (jar_path, plugin)
281
- def _little_build(self, plugin_id: str) -> Tuple[Path, Plugin]:
282
- jar_path = self.get_root_path().joinpath('target', 'pluginjars', f'{plugin_id}.jar')
211
+ def _little_build(self, plugin_id: PluginIdentifier) -> tuple[Path, Plugin]:
212
+ jar_path = self.get_root().joinpath('target', 'pluginjars', f'{plugin_id}.jar')
283
213
  if not jar_path.is_file():
284
214
  raise Exception(f'{plugin_id}: built JAR not found: {jar_path!s}')
285
215
  return jar_path, Plugin.from_jar(jar_path)
286
216
 
287
- def _plugin_path(self, plugin_id) -> Path:
288
- return self.get_main_path().joinpath(Plugin.id_to_file(plugin_id))
289
-
290
217
  def _sanitize(self, called_process_error: subprocess.CalledProcessError) -> subprocess.CalledProcessError:
291
218
  cmd = called_process_error.cmd[:]
292
219
  for i in range(len(cmd)):
@@ -294,3 +221,41 @@ class MavenPluginSet(PluginSet):
294
221
  cmd[i] = '-Dkeystore.password=<password>'
295
222
  called_process_error.cmd = ' '.join([shlex.quote(c) for c in cmd])
296
223
  return called_process_error
224
+
225
+
226
+ PluginSetBuilder = Annotated[Union[AntPluginSetBuilder, MavenPluginSetBuilder], Field(discriminator='type')]
227
+
228
+
229
+ PluginSetKind = Literal['PluginSet']
230
+
231
+
232
+ PluginSetIdentifier = str
233
+
234
+
235
+ class PluginSet(BaseModel):
236
+ kind: PluginSetKind = Field(description="This object's kind")
237
+ id: PluginSetIdentifier = Field(description='An identifier for the plugin set')
238
+ name: str = Field(description='A name for the plugin set')
239
+ builder: PluginSetBuilder = Field(description='A builder for the plugin set', title='Plugin Set Builder')
240
+
241
+ def build_plugin(self, plugin_id: PluginIdentifier, keystore_path: Path, keystore_alias: str, keystore_password=None) -> tuple[Path, Plugin]:
242
+ return self.builder.build_plugin(plugin_id, keystore_path, keystore_alias, keystore_password)
243
+
244
+ def get_builder(self) -> PluginSetBuilder:
245
+ return self.builder
246
+
247
+ def get_id(self) -> PluginSetIdentifier:
248
+ return self.id
249
+
250
+ def get_name(self) -> str:
251
+ return self.name
252
+
253
+ def has_plugin(self, plugin_id: PluginIdentifier) -> bool:
254
+ return self.get_builder().has_plugin(plugin_id)
255
+
256
+ def make_plugin(self, plugin_id: PluginIdentifier) -> Plugin:
257
+ return self.get_builder().make_plugin(plugin_id)
258
+
259
+ def _initialize(self, root: Path) -> PluginSet:
260
+ self.get_builder()._initialize(root)
261
+ return self
lockss/turtles/util.py CHANGED
@@ -28,26 +28,37 @@
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
- import json
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
33
+ from __future__ import annotations
34
+
35
+ from collections.abc import Iterable
32
36
  from pathlib import Path
33
- from typing import Any, List, TypeAlias, Union
37
+ from typing import Any, Optional, Union
38
+
39
+ from lockss.pybasic.fileutil import path
40
+ from pydantic import BaseModel
41
+
42
+
43
+ # Candidate for lockss-pybasic
44
+ PathOrStr = Union[Path, str]
45
+
46
+
47
+ class BaseModelWithRoot(BaseModel):
48
+ _root: Optional[Path]
34
49
 
35
- import jsonschema
36
- import jsonschema.exceptions
37
- import yaml
50
+ def model_post_init(self, context: Any) -> None:
51
+ self._root = None
38
52
 
53
+ def get_root(self) -> Path:
54
+ if self._root is None:
55
+ raise RuntimeError('Uninitialized root')
56
+ return self._root
39
57
 
40
- YamlT: TypeAlias = Any
58
+ def initialize(self, root: PathOrStr) -> BaseModelWithRoot:
59
+ self._root = path(root)
60
+ return self
41
61
 
42
62
 
43
- def load_and_validate(schema_path: Path, instance_path: Path, multiple: bool=False) -> Union[List[YamlT], YamlT]:
44
- with schema_path.open('r') as f:
45
- schema = json.load(f)
46
- with instance_path.open('r') as f:
47
- ret = list(yaml.safe_load_all(f)) if multiple else [yaml.safe_load(f)]
48
- for instance in ret:
49
- try:
50
- jsonschema.validate(instance, schema)
51
- except jsonschema.exceptions.ValidationError as validation_exception:
52
- raise Exception(validation_exception.message) from validation_exception
53
- return ret if multiple else ret[0]
63
+ def file_or(paths: Iterable[Path]) -> str:
64
+ return ' or '.join(map(str, paths))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lockss-turtles
3
- Version: 0.6.0.dev2
3
+ Version: 0.6.0.dev4
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
@@ -19,9 +19,8 @@ Classifier: Topic :: Software Development :: Libraries
19
19
  Classifier: Topic :: System :: Archiving
20
20
  Classifier: Topic :: Utilities
21
21
  Requires-Dist: java-manifest (>=1.1.0,<1.2.0)
22
- Requires-Dist: jsonschema (>=4.24.0,<4.25.0)
23
22
  Requires-Dist: lockss-pybasic (>=0.1.0,<0.2.0)
24
- Requires-Dist: pydantic (>=2.11.7,<3.0.0)
23
+ Requires-Dist: pydantic (>=2.11.0,<3.0.0)
25
24
  Requires-Dist: pyyaml (>=6.0.0,<6.1.0)
26
25
  Requires-Dist: xdg (>=6.0.0,<6.1.0)
27
26
  Project-URL: Documentation, https://docs.lockss.org/en/latest/software/turtles
@@ -0,0 +1,15 @@
1
+ lockss/turtles/__init__.py,sha256=Ts53KN-wrOJ3oRXXsPX3M75HTrGe7TthpeUrY47G6_E,1743
2
+ lockss/turtles/__main__.py,sha256=825geLIwXS0LKRqxifJXlLaAZrY8nllXfHzw5ch5ysM,1624
3
+ lockss/turtles/app.py,sha256=aMWuYsY88FV1YTjcnXDhKHoAYh47i819y9qa9nJMrFE,13312
4
+ lockss/turtles/cli.py,sha256=pLZAG1hmYarCp0R4vHSJcTs3G1umk2esvaLhM5JX7rg,18435
5
+ lockss/turtles/plugin.py,sha256=re44YVlzXVU8azDshxn7oHw1GdTQLLQgYwkvZlur-60,5384
6
+ lockss/turtles/plugin_registry.py,sha256=pIQOzrastTxeJsny3CV2GhhYO0iQflPdJAxSIdxAiYY,11095
7
+ lockss/turtles/plugin_set.py,sha256=Lueg3KcsJ1MEh8CFa48UbhFoO_7PB5KB6ynCpMCOI0M,11299
8
+ lockss/turtles/util.py,sha256=wGCrw_YySvyqaCzT0PItzXpkLHOCMMC_DQHhGU7zudI,2500
9
+ unittest/lockss/turtles/__init__.py,sha256=hrgWx4GaP-hhPBuRlbLndJnOQmZicKCLaP-dQTCu1sQ,3162
10
+ unittest/lockss/turtles/test_plugin_set.py,sha256=i-VM8mvrPuW2KqUlh2HPVRtgh3-F7RhmRAwcvhwpoDA,3354
11
+ lockss_turtles-0.6.0.dev4.dist-info/LICENSE,sha256=O9ONND4uDxY_jucI4jZDf2liAk05ScEJaYu-Al7EOdQ,1506
12
+ lockss_turtles-0.6.0.dev4.dist-info/METADATA,sha256=ji_iWkIEQ2UHv3xbYDPvUwZrzun9IOeSpHRNnVae9bo,39610
13
+ lockss_turtles-0.6.0.dev4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
14
+ lockss_turtles-0.6.0.dev4.dist-info/entry_points.txt,sha256=25BAVFSBRKWAWiXIGZgcr1ypt2mV7nj31Jl8WcNZZOk,51
15
+ lockss_turtles-0.6.0.dev4.dist-info/RECORD,,
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # 3. Neither the name of the copyright holder nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ from collections.abc import Callable
32
+ from pydantic import ValidationError
33
+ from pydantic_core import ErrorDetails
34
+ from typing import Any, Optional, Tuple, Union
35
+ from unittest import TestCase
36
+
37
+
38
+ Loc = Union[Tuple[str], str]
39
+
40
+
41
+ class PydanticTestCase(TestCase):
42
+
43
+ def _assertPydanticValidationError(self,
44
+ func: Callable[[], Any],
45
+ matcher: Callable[[ErrorDetails], bool],
46
+ msg: Optional[str]=None):
47
+ with self.assertRaises(ValidationError) as cm:
48
+ func()
49
+ self.fail('Expected ValidationError but did not get one')
50
+ self.assertIsInstance(cm.exception, ValidationError)
51
+ ve: ValidationError = cm.exception
52
+ for e in ve.errors():
53
+ if matcher(e):
54
+ return
55
+ self.fail(msg or f'Did not get a matching ValidationError; got:\n{"\n".join([str(e) for e in ve.errors()])}\n{ve!s}')
56
+
57
+ def assertPydanticMissing(self, func: Callable[[], Any], loc: Loc, msg=None) -> None:
58
+ if isinstance(loc, str):
59
+ loc = (loc,)
60
+ self._assertPydanticValidationError(func, lambda e: e.get('type') == 'missing' and e.get('loc') == loc)
61
+
62
+ def assertPydanticLiteralError(self, func: Callable[[], Any], loc: Loc, expected: str, msg=None) -> None:
63
+ if isinstance(loc, str):
64
+ loc = (loc,)
65
+ self._assertPydanticValidationError(func, lambda e: e.get('type') == 'literal_error' and e.get('loc') == loc and (ctx := e.get('ctx')) and ctx.get('expected') == repr(expected))
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # 3. Neither the name of the copyright holder nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ from . import PydanticTestCase
32
+ from lockss.turtles.plugin_set import AntPluginSetBuilder, MavenPluginSetBuilder, PluginSet, PluginSetBuilder
33
+
34
+
35
+ class TestPluginSet(PydanticTestCase):
36
+
37
+ def setUp(self):
38
+ pass
39
+
40
+ def testPluginSet(self):
41
+ self.assertPydanticMissing(lambda: PluginSet(), 'kind')
42
+ self.assertPydanticLiteralError(lambda: PluginSet(kind='WrongKind'), 'kind', 'PluginSet')
43
+ self.assertPydanticMissing(lambda: PluginSet(kind='PluginSet'), 'id')
44
+ self.assertPydanticMissing(lambda: PluginSet(kind='PluginSet', id='myid'), 'name')
45
+ self.assertPydanticMissing(lambda: PluginSet(kind='PluginSet', id='myset', name='My Set'), 'builder')
46
+ PluginSet(kind='PluginSet', id='myset', name='My Set', builder=AntPluginSetBuilder(type='ant'))
47
+ PluginSet(kind='PluginSet', id='myset', name='My Set', builder=MavenPluginSetBuilder(type='maven'))
48
+
49
+ class TestPluginSetBuilder(PydanticTestCase):
50
+
51
+ def doTestPluginSetBuilder(self, Pbsc: type(PluginSetBuilder), typ: str, def_main: str, def_test: str) -> None:
52
+ self.assertPydanticMissing(lambda: Pbsc(), 'type')
53
+ self.assertPydanticLiteralError(lambda: Pbsc(type='BADTYPE'), 'type', typ)
54
+ self.assertIsNone(Pbsc(type=typ)._root)
55
+ self.assertEqual(Pbsc(type=typ)._get_main(), def_main)
56
+ self.assertEqual(Pbsc(type=typ, main='mymain')._get_main(), 'mymain')
57
+ self.assertEqual(Pbsc(type=typ)._get_test(), def_test)
58
+ self.assertEqual(Pbsc(type=typ, test='mytest')._get_test(), 'mytest')
59
+
60
+ def testPluginSetBuilder(self):
61
+ self.doTestPluginSetBuilder(AntPluginSetBuilder, 'ant', 'plugins/src', 'plugins/test/src')
62
+ self.doTestPluginSetBuilder(MavenPluginSetBuilder, 'maven', 'src/main/java', 'src/test/java')
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- # Copyright (c) 2000-2024, Board of Trustees of Leland Stanford Jr. University
4
- #
5
- # Redistribution and use in source and binary forms, with or without
6
- # modification, are permitted provided that the following conditions are met:
7
- #
8
- # 1. Redistributions of source code must retain the above copyright notice,
9
- # this list of conditions and the following disclaimer.
10
- #
11
- # 2. Redistributions in binary form must reproduce the above copyright notice,
12
- # this list of conditions and the following disclaimer in the documentation
13
- # and/or other materials provided with the distribution.
14
- #
15
- # 3. Neither the name of the copyright holder nor the names of its contributors
16
- # may be used to endorse or promote products derived from this software without
17
- # specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
@@ -1,27 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://assets.lockss.org/schemas/json/plugin-registry-catalog-schema.json",
4
- "title": "Plugin Registry Catalog",
5
- "description": "LOCKSS plugin registry catalog",
6
- "type": "object",
7
- "required": [
8
- "kind",
9
- "plugin-registry-files"
10
- ],
11
- "properties": {
12
- "kind": {
13
- "description": "This object's kind",
14
- "type": "string",
15
- "const": "PluginRegistryCatalog"
16
- },
17
- "plugin-registry-files": {
18
- "description": "A list of plugin registry files",
19
- "type": "array",
20
- "minItems": 1,
21
- "uniqueItems": true,
22
- "items": {
23
- "type": "string"
24
- }
25
- }
26
- }
27
- }