lockss-turtles 0.5.0.dev4__py3-none-any.whl → 0.6.0__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.
- lockss/turtles/__init__.py +10 -30
- lockss/turtles/__main__.py +7 -3
- lockss/turtles/app.py +520 -109
- lockss/turtles/cli.py +540 -333
- lockss/turtles/plugin.py +207 -50
- lockss/turtles/plugin_registry.py +617 -189
- lockss/turtles/plugin_set.py +534 -187
- lockss/turtles/plugin_signing_credentials.py +84 -0
- lockss/turtles/util.py +70 -21
- {lockss_turtles-0.5.0.dev4.dist-info → lockss_turtles-0.6.0.dist-info}/LICENSE +1 -1
- lockss_turtles-0.6.0.dist-info/METADATA +64 -0
- lockss_turtles-0.6.0.dist-info/RECORD +18 -0
- {lockss_turtles-0.5.0.dev4.dist-info → lockss_turtles-0.6.0.dist-info}/WHEEL +1 -1
- unittest/lockss/turtles/__init__.py +106 -0
- unittest/lockss/turtles/test_plugin_registry.py +417 -0
- unittest/lockss/turtles/test_plugin_set.py +274 -0
- unittest/lockss/turtles/test_plugin_signing_credentials.py +102 -0
- CHANGELOG.rst +0 -113
- lockss/turtles/resources/__init__.py +0 -29
- lockss/turtles/resources/plugin-registry-catalog-schema.json +0 -27
- lockss/turtles/resources/plugin-registry-schema.json +0 -115
- lockss/turtles/resources/plugin-set-catalog-schema.json +0 -27
- lockss/turtles/resources/plugin-set-schema.json +0 -92
- lockss/turtles/resources/plugin-signing-credentials-schema.json +0 -27
- lockss_turtles-0.5.0.dev4.dist-info/METADATA +0 -1041
- lockss_turtles-0.5.0.dev4.dist-info/RECORD +0 -20
- {lockss_turtles-0.5.0.dev4.dist-info → lockss_turtles-0.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
"""
|
|
32
|
+
Module to represent plugin signing credentials.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Literal
|
|
37
|
+
|
|
38
|
+
from pydantic import Field
|
|
39
|
+
|
|
40
|
+
from .util import BaseModelWithRoot
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
#: A type alias for the plugin signing credentials kind.
|
|
44
|
+
PluginSigningCredentialsKind = Literal['PluginSigningCredentials']
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PluginSigningCredentials(BaseModelWithRoot):
|
|
48
|
+
"""
|
|
49
|
+
A Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to represent
|
|
50
|
+
plugin signing credentials.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
#: This object's kind.
|
|
54
|
+
kind: PluginSigningCredentialsKind = Field(title='Kind',
|
|
55
|
+
description="This object's kind")
|
|
56
|
+
|
|
57
|
+
#: Path to the plugin signing keystore.
|
|
58
|
+
plugin_signing_keystore: str = Field(alias='plugin-signing-keystore',
|
|
59
|
+
title='Plugin Signing Keystore',
|
|
60
|
+
description='A path to the plugin signing keystore')
|
|
61
|
+
|
|
62
|
+
#: Alias to use from the plugin signing keystore.
|
|
63
|
+
plugin_signing_alias: str = Field(alias='plugin-signing-alias',
|
|
64
|
+
title='Plugin Signing Alias',
|
|
65
|
+
description='The plugin signing alias to use')
|
|
66
|
+
|
|
67
|
+
def get_plugin_signing_alias(self) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Returns the alias to use from the plugin signing keystore.
|
|
70
|
+
|
|
71
|
+
:return: The plugin signing alias.
|
|
72
|
+
:rtype: str
|
|
73
|
+
"""
|
|
74
|
+
return self.plugin_signing_alias
|
|
75
|
+
|
|
76
|
+
def get_plugin_signing_keystore(self) -> Path:
|
|
77
|
+
"""
|
|
78
|
+
The file path for the plugin signing keystore; a relative path is
|
|
79
|
+
understood to be relative to the file containing the definition.
|
|
80
|
+
|
|
81
|
+
:return: The file path for the plugin signing keystore.
|
|
82
|
+
:rtype: Path
|
|
83
|
+
"""
|
|
84
|
+
return self.get_root().joinpath(self.plugin_signing_keystore)
|
lockss/turtles/util.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
# Copyright (c) 2000-
|
|
3
|
+
# Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -28,28 +28,77 @@
|
|
|
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
|
-
|
|
31
|
+
"""
|
|
32
|
+
Utility module.
|
|
33
|
+
"""
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
import
|
|
36
|
-
import yaml
|
|
35
|
+
# Remove in Python 3.14; see https://stackoverflow.com/a/33533514
|
|
36
|
+
from __future__ import annotations
|
|
37
37
|
|
|
38
|
+
from collections.abc import Iterable
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import Any, Optional, Union
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
schema = json.load(f)
|
|
42
|
-
with instance_path.open('r') as f:
|
|
43
|
-
ret = list(yaml.safe_load_all(f) if multiple else [yaml.safe_load(f)])
|
|
44
|
-
for instance in ret:
|
|
45
|
-
try:
|
|
46
|
-
jsonschema.validate(instance, schema)
|
|
47
|
-
except jsonschema.exceptions.ValidationError as validation_exception:
|
|
48
|
-
raise Exception(validation_exception.message) from validation_exception
|
|
49
|
-
return ret if multiple else ret[0]
|
|
42
|
+
from lockss.pybasic.fileutil import path
|
|
43
|
+
from pydantic import BaseModel
|
|
50
44
|
|
|
51
45
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
#: Type alias for the union of ``Path`` and ``str``.
|
|
47
|
+
PathOrStr = Union[Path, str]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BaseModelWithRoot(BaseModel):
|
|
51
|
+
"""
|
|
52
|
+
A Pydantic model with a root path which can be used to resolve relative
|
|
53
|
+
paths.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
#: An internal root path.
|
|
57
|
+
_root: Optional[Path]
|
|
58
|
+
|
|
59
|
+
def model_post_init(self, context: Any) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Pydantic post-initialization method to create the ``_root`` field.
|
|
62
|
+
"""
|
|
63
|
+
self._root = None
|
|
64
|
+
|
|
65
|
+
def get_root(self) -> Path:
|
|
66
|
+
"""
|
|
67
|
+
Returns this object's root path.
|
|
68
|
+
|
|
69
|
+
See ``initialize``.
|
|
70
|
+
|
|
71
|
+
:return: This object's root path.
|
|
72
|
+
:rtype: Path
|
|
73
|
+
:raises ValueError: If this object's ``initialize`` method was not
|
|
74
|
+
called.
|
|
75
|
+
"""
|
|
76
|
+
if self._root is None:
|
|
77
|
+
raise ValueError('Uninitialized root')
|
|
78
|
+
return self._root
|
|
79
|
+
|
|
80
|
+
def initialize(self,
|
|
81
|
+
root_path_or_str: PathOrStr) -> BaseModelWithRoot:
|
|
82
|
+
"""
|
|
83
|
+
Mandatory initialization of the root path.
|
|
84
|
+
|
|
85
|
+
:param root_path_or_str: This object's root path.
|
|
86
|
+
:type root_path_or_str: PathOrStr
|
|
87
|
+
:return: This object, for chaining.
|
|
88
|
+
:rtype: BaseModelWithRoot
|
|
89
|
+
"""
|
|
90
|
+
self._root = path(root_path_or_str)
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def file_or(paths: Iterable[Path]) -> str:
|
|
95
|
+
"""
|
|
96
|
+
Turns an iterable of file paths into a ``" or "``-separated string suitable
|
|
97
|
+
for CLI messages.
|
|
98
|
+
|
|
99
|
+
:param paths: A non-null list of file paths.
|
|
100
|
+
:type paths: Iterable[Path]
|
|
101
|
+
:return: A ``" or "``-separated string of the given file paths.
|
|
102
|
+
:rtype: str
|
|
103
|
+
"""
|
|
104
|
+
return ' or '.join(map(str, paths))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright (c) 2000-
|
|
1
|
+
Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
2
2
|
|
|
3
3
|
Redistribution and use in source and binary forms, with or without
|
|
4
4
|
modification, are permitted provided that the following conditions are met:
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: lockss-turtles
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin registries
|
|
5
|
+
License: BSD-3-Clause
|
|
6
|
+
Author: Thib Guicherd-Callin
|
|
7
|
+
Author-email: thib@cs.stanford.edu
|
|
8
|
+
Maintainer: Thib Guicherd-Callin
|
|
9
|
+
Maintainer-email: thib@cs.stanford.edu
|
|
10
|
+
Requires-Python: >=3.9,<4.0
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Framework :: Pydantic :: 2
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: System :: Archiving
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Dist: exceptiongroup (>=1.3.0,<1.4.0)
|
|
22
|
+
Requires-Dist: java-manifest (>=1.1.0,<1.2.0)
|
|
23
|
+
Requires-Dist: lockss-pybasic (>=0.1.1,<0.2.0)
|
|
24
|
+
Requires-Dist: pydantic (>=2.11.0,<2.12.0)
|
|
25
|
+
Requires-Dist: pyyaml (>=6.0.0,<6.1.0)
|
|
26
|
+
Requires-Dist: xdg (>=6.0.0,<6.1.0)
|
|
27
|
+
Project-URL: Documentation, https://docs.lockss.org/en/latest/software/turtles
|
|
28
|
+
Project-URL: Repository, https://github.com/lockss/lockss-turtles
|
|
29
|
+
Project-URL: changelog, https://github.com/lockss/lockss-turtles/blob/main/CHANGELOG.rst
|
|
30
|
+
Project-URL: issues, https://github.com/lockss/lockss-turtles/issues
|
|
31
|
+
Description-Content-Type: text/x-rst
|
|
32
|
+
|
|
33
|
+
=======
|
|
34
|
+
Turtles
|
|
35
|
+
=======
|
|
36
|
+
|
|
37
|
+
.. |RELEASE| replace:: 0.6.0
|
|
38
|
+
.. |RELEASE_DATE| replace:: 2025-10-23
|
|
39
|
+
.. |TURTLES| replace:: **Turtles**
|
|
40
|
+
|
|
41
|
+
.. image:: https://assets.lockss.org/images/logos/turtles/turtles_128x128.png
|
|
42
|
+
:alt: Turtles logo
|
|
43
|
+
:align: right
|
|
44
|
+
|
|
45
|
+
|TURTLES| is a command line tool and Python library to manage LOCKSS plugin sets and LOCKSS plugin registries.
|
|
46
|
+
|
|
47
|
+
:Latest release: |RELEASE| (|RELEASE_DATE|)
|
|
48
|
+
:Documentation: https://docs.lockss.org/en/latest/software/turtles
|
|
49
|
+
:Release notes: `CHANGELOG.rst <https://github.com/lockss/lockss-turtles/blob/main/CHANGELOG.rst>`_
|
|
50
|
+
:License: `LICENSE <https://github.com/lockss/lockss-turtles/blob/main/LICENSE>`_
|
|
51
|
+
:Repository: https://github.com/lockss/lockss-turtles
|
|
52
|
+
:Issues: https://github.com/lockss/lockss-turtles/issues
|
|
53
|
+
|
|
54
|
+
----
|
|
55
|
+
|
|
56
|
+
Quick Start::
|
|
57
|
+
|
|
58
|
+
# Install with pipx
|
|
59
|
+
pipx install lockss-turtles
|
|
60
|
+
|
|
61
|
+
# Verify installation and discover all the commands
|
|
62
|
+
turtles --help
|
|
63
|
+
|
|
64
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
lockss/turtles/__init__.py,sha256=6aGkk-abZwTPYy_tMY72hKjNJjtoDCcQvnBtILsTEsk,1821
|
|
2
|
+
lockss/turtles/__main__.py,sha256=BqRTG3dejCgLh87UJ1Os9DD9unxnQkOdUQM6eqWS3Sg,1680
|
|
3
|
+
lockss/turtles/app.py,sha256=XnBF91r-D3YGSfgIP0v9C064fH8Rc6y4I5y1arEweCQ,30520
|
|
4
|
+
lockss/turtles/cli.py,sha256=x4Ciba5fcDSNeYAhhauOeCP1FcMy5ptOts32CWG3la4,30546
|
|
5
|
+
lockss/turtles/plugin.py,sha256=fxIAaSL06uv9sjYCz2wTQjn_92qPK3EvGNnbkpeSZcA,10972
|
|
6
|
+
lockss/turtles/plugin_registry.py,sha256=8BR6CrqY8hh4zpy13B8gZ9qpCMASlOxvX6g8VmpWeMc,26864
|
|
7
|
+
lockss/turtles/plugin_set.py,sha256=QCUWh56NV5YMQmZIz3-9DYHZI42BC3Aain5mxax7x9o,24668
|
|
8
|
+
lockss/turtles/plugin_signing_credentials.py,sha256=EekUJeXvbfRcrVHCsvvbGBO07bSBzEgVL1Ck4TUbe8k,3415
|
|
9
|
+
lockss/turtles/util.py,sha256=sBvB96BBED_yVZOybcf-sIwldNm9wstgWssnhugpww0,3536
|
|
10
|
+
unittest/lockss/turtles/__init__.py,sha256=ude-nT_uHF0lCD9qKrzedm8QvwqTiNjogpyz8SJudOQ,5598
|
|
11
|
+
unittest/lockss/turtles/test_plugin_registry.py,sha256=NXQyvvV3u1Pk3h5e-W7F93NIL6rYrv3bN_N4TFuZ0_U,18644
|
|
12
|
+
unittest/lockss/turtles/test_plugin_set.py,sha256=SJ2IO3sVOkm1zRr4a9SbyMhKpyKZAzoDV1oVfya3eEU,10949
|
|
13
|
+
unittest/lockss/turtles/test_plugin_signing_credentials.py,sha256=oxAKxeIEATNu3dIajcAFy-gEChMDCHSSDjZotuAB49o,5269
|
|
14
|
+
lockss_turtles-0.6.0.dist-info/LICENSE,sha256=O9ONND4uDxY_jucI4jZDf2liAk05ScEJaYu-Al7EOdQ,1506
|
|
15
|
+
lockss_turtles-0.6.0.dist-info/METADATA,sha256=AUSE2g09EKcSqA34-m-TOwdsmjxXuBMesC_sBicF6Lo,2305
|
|
16
|
+
lockss_turtles-0.6.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
+
lockss_turtles-0.6.0.dist-info/entry_points.txt,sha256=25BAVFSBRKWAWiXIGZgcr1ypt2mV7nj31Jl8WcNZZOk,51
|
|
18
|
+
lockss_turtles-0.6.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,106 @@
|
|
|
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 pathlib import Path
|
|
33
|
+
from pydantic import ValidationError
|
|
34
|
+
from pydantic_core import ErrorDetails
|
|
35
|
+
from typing import Any, Optional, Tuple, Union
|
|
36
|
+
from unittest import TestCase
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
ROOT: Path = Path('.').absolute()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Loc = Union[Tuple[str], str]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PydanticTestCase(TestCase):
|
|
46
|
+
|
|
47
|
+
def assertPydanticListType(self, func: Callable[[], Any], loc_tuple_or_str: Loc, msg=None) -> None:
|
|
48
|
+
self._assertPydanticValidationError(func,
|
|
49
|
+
lambda e: e.get('type') == 'list_type' \
|
|
50
|
+
and e.get('loc') == self._loc(loc_tuple_or_str),
|
|
51
|
+
msg=msg)
|
|
52
|
+
|
|
53
|
+
def assertPydanticLiteralError(self, func: Callable[[], Any], loc_tuple_or_str: Loc, expected: Union[Tuple[str], str], msg=None) -> None:
|
|
54
|
+
if isinstance(expected, str):
|
|
55
|
+
exp = f"'{expected}'"
|
|
56
|
+
elif len(expected) == 0:
|
|
57
|
+
raise RuntimeError(f"'expected' cannot be an empty tuple")
|
|
58
|
+
elif len(expected) == 1:
|
|
59
|
+
exp = f"'{expected[0]}'"
|
|
60
|
+
else:
|
|
61
|
+
exp = f'''{", ".join(f"'{e}'" for e in expected[:-1])} or '{expected[-1]}\''''
|
|
62
|
+
self._assertPydanticValidationError(func,
|
|
63
|
+
lambda e: e.get('type') == 'literal_error' \
|
|
64
|
+
and e.get('loc') == self._loc(loc_tuple_or_str) \
|
|
65
|
+
and (ctx := e.get('ctx')) \
|
|
66
|
+
and ctx.get('expected') == exp,
|
|
67
|
+
msg=msg)
|
|
68
|
+
|
|
69
|
+
def assertPydanticModelAttributesType(self, func: Callable[[], Any], loc_tuple_or_str: Loc, msg=None) -> None:
|
|
70
|
+
self._assertPydanticValidationError(func,
|
|
71
|
+
lambda e: e.get('type') == 'model_attributes_type' \
|
|
72
|
+
and e.get('loc') == self._loc(loc_tuple_or_str),
|
|
73
|
+
msg=msg)
|
|
74
|
+
|
|
75
|
+
def assertPydanticMissing(self, func: Callable[[], Any], loc_tuple_or_str: Loc, msg=None) -> None:
|
|
76
|
+
self._assertPydanticValidationError(func,
|
|
77
|
+
lambda e: e.get('type') == 'missing' \
|
|
78
|
+
and e.get('loc') == self._loc(loc_tuple_or_str),
|
|
79
|
+
msg=msg)
|
|
80
|
+
|
|
81
|
+
def assertPydanticStringType(self, func: Callable[[], Any], loc_tuple_or_str: Loc, msg=None) -> None:
|
|
82
|
+
self._assertPydanticValidationError(func,
|
|
83
|
+
lambda e: e.get('type') == 'string_type' \
|
|
84
|
+
and e.get('loc') == self._loc(loc_tuple_or_str),
|
|
85
|
+
msg=msg)
|
|
86
|
+
|
|
87
|
+
def assertPydanticTooShort(self, func: Callable[[], Any], loc_tuple_or_str: Loc, msg=None) -> None:
|
|
88
|
+
self._assertPydanticValidationError(func,
|
|
89
|
+
lambda e: e.get('type') == 'too_short' \
|
|
90
|
+
and e.get('loc') == self._loc(loc_tuple_or_str),
|
|
91
|
+
msg=msg)
|
|
92
|
+
|
|
93
|
+
def _assertPydanticValidationError(self,
|
|
94
|
+
func: Callable[[], Any],
|
|
95
|
+
matcher: Callable[[ErrorDetails], bool],
|
|
96
|
+
msg: Optional[str]=None) -> None:
|
|
97
|
+
with self.assertRaises(ValidationError) as cm:
|
|
98
|
+
func()
|
|
99
|
+
ve: ValidationError = cm.exception
|
|
100
|
+
for e in ve.errors():
|
|
101
|
+
if matcher(e):
|
|
102
|
+
return
|
|
103
|
+
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}')
|
|
104
|
+
|
|
105
|
+
def _loc(self, loc_tuple_or_str: Loc) -> tuple[str]:
|
|
106
|
+
return (loc_tuple_or_str,) if isinstance(loc_tuple_or_str, str) else loc_tuple_or_str
|