lockss-pybasic 0.2.0.dev2__tar.gz → 0.2.0.dev4__tar.gz
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_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/PKG-INFO +1 -1
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/pyproject.toml +1 -1
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/src/lockss/pybasic/__init__.py +1 -1
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/src/lockss/pybasic/cliutil.py +34 -29
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/CHANGELOG.rst +0 -0
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/LICENSE +0 -0
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/README.rst +0 -0
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/src/lockss/pybasic/errorutil.py +0 -0
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/src/lockss/pybasic/fileutil.py +0 -0
- {lockss_pybasic-0.2.0.dev2 → lockss_pybasic-0.2.0.dev4}/src/lockss/pybasic/outpututil.py +0 -0
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
[project]
|
|
30
30
|
name = "lockss-pybasic"
|
|
31
|
-
version = "0.2.0-
|
|
31
|
+
version = "0.2.0-dev4" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
|
|
32
32
|
description = "Basic Python utilities"
|
|
33
33
|
license = { text = "BSD-3-Clause" }
|
|
34
34
|
readme = "README.rst"
|
|
@@ -34,47 +34,52 @@ Command line utilities.
|
|
|
34
34
|
|
|
35
35
|
from collections.abc import Callable
|
|
36
36
|
import sys
|
|
37
|
-
from typing import Any, Dict, Generic, Optional, TypeVar
|
|
37
|
+
from typing import Any, ClassVar, Dict, Generic, Optional, TypeVar, TYPE_CHECKING
|
|
38
38
|
|
|
39
|
-
from pydantic.v1 import BaseModel
|
|
39
|
+
from pydantic.v1 import BaseModel, PrivateAttr, create_model
|
|
40
|
+
from pydantic.v1.fields import FieldInfo
|
|
40
41
|
from pydantic_argparse import ArgumentParser
|
|
41
42
|
from pydantic_argparse.argparse.actions import SubParsersAction
|
|
42
43
|
from rich_argparse import RichHelpFormatter
|
|
43
44
|
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from _typeshed import SupportsWrite as SupportsWriteStr
|
|
47
|
+
else:
|
|
48
|
+
SupportsWriteStr = Any
|
|
44
49
|
|
|
45
|
-
class ActionCommand(Callable, BaseModel):
|
|
46
|
-
"""
|
|
47
|
-
Base class for a pydantic-argparse style command.
|
|
48
|
-
"""
|
|
49
|
-
pass
|
|
50
50
|
|
|
51
|
+
class StringCommand(BaseModel):
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
A pydantic-argparse style command that prints a string.
|
|
53
|
+
def display(self, file: SupportsWriteStr=sys.stdout) -> None:
|
|
54
|
+
print(getattr(self, 'display_string'), file=file)
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
@staticmethod
|
|
57
|
+
def make(model_name: str, option_name: str, description: str, display_string: str):
|
|
58
|
+
return create_model(model_name,
|
|
59
|
+
__base__=StringCommand,
|
|
60
|
+
**{option_name: (Optional[bool], FieldInfo(False, description=description)),
|
|
61
|
+
display_string: PrivateAttr(display_string)})
|
|
57
62
|
|
|
58
|
-
.. code-block:: python
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
copyright: Optional[StringCommand.type(my_copyright_string)] = Field(description=COPYRIGHT_DESCRIPTION)
|
|
64
|
+
class CopyrightCommand(StringCommand):
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
@staticmethod
|
|
67
|
+
def make(display_string: str):
|
|
68
|
+
return StringCommand.make('CopyrightCommand', 'copyright', 'print the copyright and exit', display_string)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class LicenseCommand(StringCommand):
|
|
66
72
|
|
|
67
73
|
@staticmethod
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
def __call__(self, file=sys.stdout, **kwargs):
|
|
71
|
-
print(display_str, file=file)
|
|
72
|
-
return _StringCommand
|
|
74
|
+
def make(display_string: str):
|
|
75
|
+
return StringCommand.make('LicenseCommand', 'license', 'print the software license and exit', display_string)
|
|
73
76
|
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
class VersionCommand(StringCommand):
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def make(display_string: str):
|
|
82
|
+
return StringCommand.make('VersionCommand', 'version', 'print the version number and exit', display_string)
|
|
78
83
|
|
|
79
84
|
|
|
80
85
|
BaseModelT = TypeVar('BaseModelT', bound=BaseModel)
|
|
@@ -195,7 +200,7 @@ def at_most_one_from_enum(model_cls: type[BaseModel], values: Dict[str, Any], en
|
|
|
195
200
|
enum_names = [field_name for field_name, model_field in model_cls.__fields__.items() if model_field.field_info.extra.get('enum') == enum_cls]
|
|
196
201
|
ret = [field_name for field_name in enum_names if values.get(field_name)]
|
|
197
202
|
if (length := len(ret)) > 1:
|
|
198
|
-
raise ValueError(f'at most one of {', '.join([option_name(model_cls, enum_name) for enum_name in enum_names])}
|
|
203
|
+
raise ValueError(f'at most one of {', '.join([option_name(model_cls, enum_name) for enum_name in enum_names])} allowed; got {length} ({', '.join([option_name(enum_name) for enum_name in ret])})')
|
|
199
204
|
return values
|
|
200
205
|
|
|
201
206
|
|
|
@@ -221,19 +226,19 @@ def get_from_enum(model_inst, enum_cls, default=None):
|
|
|
221
226
|
|
|
222
227
|
def at_most_one(model_cls: type[BaseModel], values: Dict[str, Any], *names: str):
|
|
223
228
|
if (length := _matchy_length(values, *names)) > 1:
|
|
224
|
-
raise ValueError(f'at most one of {', '.join([option_name(model_cls, name) for name in names])}
|
|
229
|
+
raise ValueError(f'at most one of {', '.join([option_name(model_cls, name) for name in names])} allowed; got {length}')
|
|
225
230
|
return values
|
|
226
231
|
|
|
227
232
|
|
|
228
233
|
def exactly_one(model_cls: type[BaseModel], values: Dict[str, Any], *names: str):
|
|
229
234
|
if (length := _matchy_length(values, *names)) != 1:
|
|
230
|
-
raise ValueError(f'exactly one of {', '.join([option_name(model_cls, name) for name in names])}
|
|
235
|
+
raise ValueError(f'exactly one of {', '.join([option_name(model_cls, name) for name in names])} required; got {length}')
|
|
231
236
|
return values
|
|
232
237
|
|
|
233
238
|
|
|
234
239
|
def one_or_more(model_cls: type[BaseModel], values: Dict[str, Any], *names: str):
|
|
235
240
|
if _matchy_length(values, *names) == 0:
|
|
236
|
-
raise ValueError(f'one or more of {', '.join([option_name(model_cls, name) for name in names])}
|
|
241
|
+
raise ValueError(f'one or more of {', '.join([option_name(model_cls, name) for name in names])} required')
|
|
237
242
|
return values
|
|
238
243
|
|
|
239
244
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|