lockss-pybasic 0.1.0.dev21__py3-none-any.whl → 0.1.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.
@@ -36,4 +36,4 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36
36
  POSSIBILITY OF SUCH DAMAGE.
37
37
  '''.strip()
38
38
 
39
- __version__ = '0.1.0-dev21'
39
+ __version__ = '0.1.0-dev23'
lockss/pybasic/cliutil.py CHANGED
@@ -38,21 +38,36 @@ from typing import Any, Dict, Generic, Optional, TypeVar
38
38
 
39
39
  from pydantic.v1 import BaseModel
40
40
  from pydantic_argparse import ArgumentParser
41
+ from pydantic_argparse.argparse.actions import SubParsersAction
41
42
  from rich_argparse import RichHelpFormatter
42
43
 
44
+
43
45
  class ActionCommand(Callable, BaseModel):
44
46
  """
45
- Base class for a Pydantic-Argparse command
47
+ Base class for a pydantic-argparse style command.
46
48
  """
47
49
  pass
48
50
 
49
51
 
50
52
  class StringCommand(ActionCommand):
53
+ """
54
+ A pydantic-argparse style command that prints a string.
55
+
56
+ Example of use:
57
+
58
+ .. code-block:: python
59
+
60
+ class MyCliModel(BaseModel):
61
+ copyright: Optional[StringCommand.type(my_copyright_string)] = Field(description=COPYRIGHT_DESCRIPTION)
62
+
63
+ See also the convenience constants ``COPYRIGHT_DESCRIPTION``,
64
+ ``LICENSE_DESCRIPTION``, and ``VERSION_DESCRIPTION``.
65
+ """
51
66
 
52
67
  @staticmethod
53
68
  def type(display_str: str):
54
69
  class _StringCommand(StringCommand):
55
- def __call__(self, file=sys.stdout):
70
+ def __call__(self, file=sys.stdout, **kwargs):
56
71
  print(display_str, file=file)
57
72
  return _StringCommand
58
73
 
@@ -77,20 +92,21 @@ class BaseCli(Generic[BaseModelT]):
77
92
  method named ``_x_y_z``.
78
93
  """
79
94
 
80
- def __init__(self, **extra):
95
+ def __init__(self, **kwargs):
81
96
  """
82
- Makes a new BaseCli instance.
83
-
84
- :param extra: Keyword arguments. Must include ``model``, the
85
- ``BaseModel`` type corresponding to ``BaseModelT``. Must
86
- include ``prog``, the command-line name of the program.
87
- Must include ``description``, the description string of
88
- the program.
97
+ Constructs a new ``BaseCli`` instance.
98
+
99
+ :param kwargs: Keyword arguments. Must include ``model``, the
100
+ ``BaseModel`` type corresponding to ``BaseModelT``. Must
101
+ include ``prog``, the command-line name of the program.
102
+ Must include ``description``, the description string of
103
+ the program.
104
+ :type kwargs: Dict[str, Any]
89
105
  """
90
106
  super().__init__()
91
107
  self._args: Optional[BaseModelT] = None
92
108
  self._parser: Optional[ArgumentParser] = None
93
- self.extra: Dict[str, Any] = dict(**extra)
109
+ self.extra: Dict[str, Any] = dict(**kwargs)
94
110
 
95
111
  def run(self) -> None:
96
112
  """
@@ -103,8 +119,6 @@ class BaseCli(Generic[BaseModelT]):
103
119
  * Stores the Pydantic-Argparse parsed arguments in ``self.args``.
104
120
 
105
121
  * Calls ``dispatch()``.
106
-
107
- :return: Nothing.
108
122
  """
109
123
  self._parser: ArgumentParser = ArgumentParser(model=self.extra.get('model'),
110
124
  prog=self.extra.get('prog'),
@@ -118,8 +132,6 @@ class BaseCli(Generic[BaseModelT]):
118
132
  Dispatches from the first field ``x_y_z`` in ``self.args`` that is a
119
133
  command (i.e. whose value derives from ``BaseModel``) to a method
120
134
  called ``_x_y_z``.
121
-
122
- :return: Nothing.
123
135
  """
124
136
  field_names = self._args.__class__.__fields__.keys()
125
137
  for field_name in field_names:
@@ -135,20 +147,34 @@ class BaseCli(Generic[BaseModelT]):
135
147
  self._parser.error(f'unknown command; expected one of {', '.join(field_names)}')
136
148
 
137
149
  def _initialize_rich_argparse(self) -> None:
138
- for cls in [RichHelpFormatter]:
139
- cls.styles.update({
140
- 'argparse.args': f'bold {cls.styles["argparse.args"]}',
141
- 'argparse.groups': f'bold {cls.styles["argparse.groups"]}',
142
- 'argparse.metavar': f'bold {cls.styles["argparse.metavar"]}',
143
- 'argparse.prog': f'bold {cls.styles["argparse.prog"]}',
144
- })
150
+ """
151
+ Initializes `rich-argparse <https://pypi.org/project/rich-argparse/>`_
152
+ for this instance.
153
+ """
154
+ self._initialize_rich_argparse_styles()
145
155
  def __add_formatter_class(container):
146
156
  container.formatter_class = RichHelpFormatter
147
- if container._subparsers:
148
- for x in container._subparsers:
149
- __add_formatter_class(x)
157
+ if hasattr(container, '_actions'):
158
+ for action in container._actions:
159
+ if issubclass(type(action), SubParsersAction):
160
+ for subaction in action.choices.values():
161
+ __add_formatter_class(subaction)
150
162
  __add_formatter_class(self._parser)
151
163
 
164
+ def _initialize_rich_argparse_styles(self) -> None:
165
+ # See https://github.com/hamdanal/rich-argparse#customize-the-colors
166
+ for cls in [RichHelpFormatter]:
167
+ cls.styles.update({
168
+ 'argparse.args': 'bold cyan', # for positional-arguments and --options (e.g "--help")
169
+ 'argparse.groups': 'underline dark_orange', # for group names (e.g. "positional arguments")
170
+ 'argparse.help': 'default', # for argument's help text (e.g. "show this help message and exit")
171
+ 'argparse.metavar': 'italic dark_cyan', # for metavariables (e.g. "FILE" in "--file FILE")
172
+ 'argparse.prog': 'bold grey50', # for %(prog)s in the usage (e.g. "foo" in "Usage: foo [options]")
173
+ 'argparse.syntax': 'bold', # for highlights of back-tick quoted text (e.g. "`some text`")
174
+ 'argparse.text': 'default', # for descriptions, epilog, and --version (e.g. "A program to foo")
175
+ 'argparse.default': 'italic', # for %(default)s in the help (e.g. "Value" in "(default: Value)")
176
+ })
177
+
152
178
 
153
179
  def at_most_one_from_enum(model_cls, values: Dict[str, Any], enum_cls) -> Dict[str, Any]:
154
180
  """
@@ -208,7 +234,7 @@ def one_or_more(values: Dict[str, Any], *names: str):
208
234
  return values
209
235
 
210
236
 
211
- def option_name(name: str):
237
+ def option_name(name: str) -> str:
212
238
  return f'{('-' if len(name) == 1 else '--')}{name.replace('_', '-')}'
213
239
 
214
240
 
@@ -34,9 +34,12 @@ Error and exception utilities.
34
34
 
35
35
  class InternalError(RuntimeError):
36
36
  """
37
- A simple ``RuntimeError`` with the message ``internal error``, intended for
38
- situations that should never occur.
37
+ A simple no-arg ``RuntimeError`` with the message ``internal error``,
38
+ intended for situations that should never occur.
39
39
  """
40
40
 
41
- def __init__(self, *args: object) -> None:
41
+ def __init__(self) -> None:
42
+ """
43
+ Constructs a new ``InternalError`` instance.
44
+ """
42
45
  super().__init__('internal error')
@@ -40,10 +40,12 @@ from typing import List, Union
40
40
  def file_lines(fpath: Path) -> List[str]:
41
41
  """
42
42
  Returns a list of lines from the given file, with '#' comments and leading
43
- trailing whitespace removed, ignoring empty lines.
43
+ and trailing whitespace removed, ignoring empty lines.
44
44
 
45
- :param fpath: A ``Path`` to a file (``-`` for ``sys.stdin``).
46
- :return: A list of lines.
45
+ :param fpath: A ``Path`` to a file, ``-`` for ``sys.stdin``.
46
+ :type fpath: Path
47
+ :return: A list of processed lines.
48
+ :rtype: List[str]
47
49
  """
48
50
  f = None
49
51
  try:
@@ -62,7 +64,9 @@ def path(purepath_or_string: Union[PurePath, str]) -> Path:
62
64
 
63
65
  :param purepath_or_string: A ``PurePath`` (or a ``str`` from which to create
64
66
  a ``Path``).
65
- :return: An expanded and resolved ``Path``.
67
+ :type purepath_or_string: Union[PurePath, str]
68
+ :return: An expanded and resolved ``Path``.
69
+ :rtype: Path
66
70
  """
67
71
  if not issubclass(type(purepath_or_string), PurePath):
68
72
  purepath_or_string = Path(purepath_or_string)
@@ -32,10 +32,8 @@
32
32
  Utilities to work with 'tabulate'.
33
33
  """
34
34
 
35
-
36
35
  from enum import Enum
37
36
 
38
-
39
37
  from pydantic.v1 import BaseModel, Field, validator
40
38
  import tabulate
41
39
  from typing import Optional
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.3
2
+ Name: lockss-pybasic
3
+ Version: 0.1.0.dev23
4
+ Summary: Basic Python utilities
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 :: 4 - Beta
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: pydantic (>=2.11.0,<3.0.0)
22
+ Requires-Dist: pydantic-argparse (>=0.10.0,<0.11.0)
23
+ Requires-Dist: rich-argparse (>=1.7.0,<1.8.0)
24
+ Requires-Dist: tabulate (>=0.9.0,<0.10.0)
25
+ Project-URL: Repository, https://github.com/lockss/lockss-pybasic
26
+ Description-Content-Type: text/x-rst
27
+
28
+ ==============
29
+ lockss-pybasic
30
+ ==============
31
+
32
+ .. |RELEASE| replace:: 0.1.0-dev23
33
+ .. |RELEASE_DATE| replace:: ?
34
+
35
+ **Latest release:** |RELEASE| (|RELEASE_DATE|)
36
+
37
+ ``lockss-pybasic`` provides basic utilities for various LOCKSS projects written in Python.
38
+
39
+ -------
40
+ Modules
41
+ -------
42
+
43
+ ``lockss.pybasic.cliutil``
44
+ Command line utilities.
45
+
46
+ * ``BaseCli`` is a base class for command line interfaces that uses `pydantic-argparse <https://pypi.org/project/pydantic-argparse/>`_ to define arguments and subcommands, and `rich-argparse <https://pypi.org/project/rich-argparse/>`_ to display help messages. For each command ``x-y-z`` induced by a ``pydantic-argparse`` field named ``x_y_z`` deriving from ``BaseModel`` , the ``dispatch()`` method expects a method named ``_x_y_z``.
47
+
48
+ * ``StringCommand`` provides a `pydantic-argparse <https://pypi.org/project/pydantic-argparse/>`_ way to define a subcommand whose only purpose is printing a string, with ``COPYRIGHT_DESCRIPTION``, ``LICENSE_DESCRIPTION`` and ``VERSION_DESCRIPTION`` constants provided for convenience. Example:
49
+
50
+ .. code-block:: python
51
+
52
+ class MyCliModel(BaseModel):
53
+ copyright: Optional[StringCommand.type(my_copyright_string)] = Field(description=COPYRIGHT_DESCRIPTION)
54
+
55
+ * ``at_most_one_from_enum`` and ``get_from_enum`` provide a facility for defining a `pydantic-argparse <https://pypi.org/project/pydantic-argparse/>`_ model that defines one command line option per constant of an ``Enum``, using an ``enum`` keyword argument in the ``Field`` definition. Example:
56
+
57
+ .. code-block:: python
58
+
59
+ class Orientation(Enum):
60
+ horizontal = 1
61
+ vertical = 2
62
+ diagonal = 3
63
+
64
+ DEFAULT_ORIENTATION = Orientation.horizontal
65
+
66
+ class MyCliModel(BaseModel):
67
+ diagonal: Optional[bool] = Field(False, description='display diagonally', enum=Orientation)
68
+ horizontal: Optional[bool] = Field(False, description='display horizontally', enum=Orientation)
69
+ unrelated: Optional[bool] = Field(...)
70
+ vertical: Optional[bool] = Field(False, description='display vertically', enum=Orientation)
71
+
72
+ @root_validator
73
+ def _at_most_one_orientation(cls, values):
74
+ return at_most_one_from_enum(cls, values, Orientation)
75
+
76
+ def get_orientation(self) -> Orientation:
77
+ return get_from_enum(self, Orientation, DEFAULT_ORIENTATION)
78
+
79
+ ``lockss.pybasic.errorutil``
80
+ Error and exception utilities.
81
+
82
+ * ``InternalError`` is a no-arg subclass of ``RuntimeError``.
83
+
84
+ ``lockss.pybasic.fileutil``
85
+ File and path utilities.
86
+
87
+ * ``file_lines`` returns the non-empty lines of a file stripped of comments that begin with ``#`` and run to the end of a line.
88
+
89
+ * ``path`` takes a string or ``PurePath`` and returns a ``Path`` for which ``Path.expanduser()`` and ``Path.resolve()`` have been called.
90
+
91
+ ``lockss.pybasic.outpututil``
92
+ Utilities to work with `tabulate <https://pypi.org/project/tabulate/>`_.
93
+
94
+ * ``OutputFormat`` is an ``Enum`` of all the output formats from ``tabulate.tabulate_formats``.
95
+
96
+ * ``OutputFormatOptions`` defines a `pydantic-argparse <https://pypi.org/project/pydantic-argparse/>`_ model for setting an output format from ``OutputFormat``.
97
+
98
+ -------------
99
+ Release Notes
100
+ -------------
101
+
102
+ See `<CHANGELOG.rst>`_.
@@ -0,0 +1,9 @@
1
+ lockss/pybasic/__init__.py,sha256=jmOhJI5PiH3jLMWLGSCBYvv8hV2yG9PTPUfvYGtP1oU,1679
2
+ lockss/pybasic/cliutil.py,sha256=Q7WNFbtZzMBfEXjhqsUJlYOdsKYQm0Mgerf7aVe5VK8,10347
3
+ lockss/pybasic/errorutil.py,sha256=XI84PScZ851_-gfoazivJ8ceieMYWaxQr7qih5ltga0,1951
4
+ lockss/pybasic/fileutil.py,sha256=BpdoPWL70xYTuhyQRBEurScRVnPQg0mX-XW8yyKPGjw,2958
5
+ lockss/pybasic/outpututil.py,sha256=JyXKXlkaCcCGvonUvmDGRdI6PxwN5t7DPVIk64S8-2g,2345
6
+ lockss_pybasic-0.1.0.dev23.dist-info/LICENSE,sha256=O9ONND4uDxY_jucI4jZDf2liAk05ScEJaYu-Al7EOdQ,1506
7
+ lockss_pybasic-0.1.0.dev23.dist-info/METADATA,sha256=viCY_etc3kouRMBzEAcl44-zfDWo2Fkzk767I91jBKM,4372
8
+ lockss_pybasic-0.1.0.dev23.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
9
+ lockss_pybasic-0.1.0.dev23.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: lockss-pybasic
3
- Version: 0.1.0.dev21
4
- Summary: Basic Python utilities
5
- License: BSD-3-Clause
6
- Author: Thib Guicherd-Callin
7
- Author-email: thib@cs.stanford.edu
8
- Requires-Python: >=3.9,<4.0
9
- Classifier: License :: OSI Approved :: BSD License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.9
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Requires-Dist: pydantic (>=2.11.0,<3.0.0)
17
- Requires-Dist: pydantic-argparse (>=0.10.0,<0.11.0)
18
- Requires-Dist: rich-argparse (>=1.7.0,<1.8.0)
19
- Requires-Dist: tabulate (>=0.9.0,<0.10.0)
20
- Description-Content-Type: text/x-rst
21
-
22
-
@@ -1,9 +0,0 @@
1
- lockss/pybasic/__init__.py,sha256=2ouuP5yYV-Y0lBWrou7oZwUhpy209YP-YuZrhiS2JSM,1679
2
- lockss/pybasic/cliutil.py,sha256=CRTSVONGiXBjLv1gIUqMsSQ7aJqAR73qqxp-fctLjT8,8800
3
- lockss/pybasic/errorutil.py,sha256=OJqRGOjzKK8_OTJCq77u0_XZ5ti2BoVidk5jSTMB6xg,1882
4
- lockss/pybasic/fileutil.py,sha256=O_EcxobrHPLeD7lAlvtYOkd0R2NOIvpGK6mZOZTxyG4,2856
5
- lockss/pybasic/outpututil.py,sha256=S2Jpcj7IctXoA10OFGueNzcd7fMjj6RARpc6s15_xLY,2347
6
- lockss_pybasic-0.1.0.dev21.dist-info/LICENSE,sha256=O9ONND4uDxY_jucI4jZDf2liAk05ScEJaYu-Al7EOdQ,1506
7
- lockss_pybasic-0.1.0.dev21.dist-info/METADATA,sha256=IQMheXQW0kx13QurFBcARUkFFnUKJVKFQSCOYvgGjzA,784
8
- lockss_pybasic-0.1.0.dev21.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
9
- lockss_pybasic-0.1.0.dev21.dist-info/RECORD,,