py-arguments 1.0.1__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.
- py_arguments-1.0.1.dist-info/METADATA +153 -0
- py_arguments-1.0.1.dist-info/RECORD +6 -0
- py_arguments-1.0.1.dist-info/WHEEL +5 -0
- py_arguments-1.0.1.dist-info/top_level.txt +1 -0
- pyarg/__init__.py +5 -0
- pyarg/parser.py +118 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-arguments
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Description-Content-Type: text/markdown
|
|
5
|
+
Provides-Extra: development
|
|
6
|
+
Requires-Dist: pre-commit; extra == "development"
|
|
7
|
+
|
|
8
|
+
# PyArg
|
|
9
|
+
|
|
10
|
+
A small layer on top of `argparse` to build CLIs with sub-commands by **reusing parser classes**.
|
|
11
|
+
|
|
12
|
+
Each parser is a class. You define its arguments in one method, and then plug that class into another parser as a
|
|
13
|
+
command, without rewriting the arguments.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For development (changes apply without reinstalling):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Python 3.10+ and has no dependencies.
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
Create a class that inherits from `PyArg` and define the arguments in `setup_arguments`:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pyarg import PyArg
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Login(PyArg):
|
|
38
|
+
def setup_arguments(self) -> None:
|
|
39
|
+
self.add_argument("--username", "-u")
|
|
40
|
+
self.add_argument("--password", "-p")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
parser = Login(name="Login")
|
|
44
|
+
print(parser.get_arguments())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```console
|
|
48
|
+
$ python main.py -u ana -p 123
|
|
49
|
+
{'username': 'ana', 'password': '123'}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Since `PyArg` inherits from `argparse.ArgumentParser`, everything from `argparse` still works
|
|
53
|
+
(`add_argument`, `add_mutually_exclusive_group`, `type`, `nargs`, `choices`, etc.).
|
|
54
|
+
|
|
55
|
+
## Sub-commands
|
|
56
|
+
|
|
57
|
+
Use `add_command_parser` to attach parsers to a main one. Each command is named after the parser `name`, converted to
|
|
58
|
+
lowercase with spaces replaced by `-` (`"Make Directory"` becomes `make-directory`).
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from pyarg import PyArg
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Login(PyArg):
|
|
65
|
+
def setup_arguments(self) -> None:
|
|
66
|
+
self.add_argument("--username", "-u")
|
|
67
|
+
self.add_argument("--password", "-p")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Logout(PyArg):
|
|
71
|
+
def setup_arguments(self) -> None:
|
|
72
|
+
self.add_argument("--all", action="store_true")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class App(PyArg):
|
|
76
|
+
def setup_arguments(self) -> None:
|
|
77
|
+
pass # The main parser can have its own arguments too.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
app = App(name="My App", description="Demo app.")
|
|
81
|
+
app.add_command_parser(Login(name="Login", description="Sign in."))
|
|
82
|
+
app.add_command_parser(Logout(name="Logout"), description="Sign out.")
|
|
83
|
+
|
|
84
|
+
print(app.get_arguments())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```console
|
|
88
|
+
$ python main.py login -u ana -p 123
|
|
89
|
+
{'command': 'login', 'username': 'ana', 'password': '123'}
|
|
90
|
+
|
|
91
|
+
$ python main.py logout --all
|
|
92
|
+
{'command': 'logout', 'all': True}
|
|
93
|
+
|
|
94
|
+
$ python main.py -h
|
|
95
|
+
usage: My App [-h] {login,logout} ...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The chosen command is available under the `command` key.
|
|
99
|
+
|
|
100
|
+
## Reusing arguments
|
|
101
|
+
|
|
102
|
+
Arguments from other parsers can be inherited with `parents`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
class Common(PyArg):
|
|
106
|
+
def setup_arguments(self) -> None:
|
|
107
|
+
self.add_argument("--verbose", "-v", action="store_true")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Build(PyArg):
|
|
111
|
+
def setup_arguments(self) -> None:
|
|
112
|
+
self.add_argument("--target")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
build = Build(name="Build", parents=[Common(name="Common", add_help=False)])
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Use `add_help=False` on the parent parser so the `-h` option is not registered twice.
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### `PyArg(name, description=None, parents=[], **kwargs)`
|
|
123
|
+
|
|
124
|
+
Abstract class. It cannot be instantiated directly, you must inherit from it and implement `setup_arguments`.
|
|
125
|
+
|
|
126
|
+
- `name`: parser name. Used as `prog` in the help text and to build the command name.
|
|
127
|
+
- `description`: text shown in `-h`.
|
|
128
|
+
- `parents`: other parsers whose arguments are copied into this one.
|
|
129
|
+
- `**kwargs`: forwarded to `argparse.ArgumentParser`. The `prog`, `description`, `parents` and `conflict_handler`
|
|
130
|
+
keys are ignored, as `conflict_handler` is always `"resolve"`.
|
|
131
|
+
|
|
132
|
+
### `setup_arguments(self) -> None`
|
|
133
|
+
|
|
134
|
+
Abstract method. Define all the arguments of the parser here. It runs at the end of `__init__`.
|
|
135
|
+
|
|
136
|
+
### `command_name`
|
|
137
|
+
|
|
138
|
+
Read-only property with the command name derived from `name`.
|
|
139
|
+
|
|
140
|
+
### `add_command_parser(parser, description=None, script_entrypoint=None)`
|
|
141
|
+
|
|
142
|
+
Adds `parser` as a sub-command. If `description` is not set, the parser's own description is used.
|
|
143
|
+
|
|
144
|
+
`script_entrypoint` stores a callable on the command, but `get_arguments` does not call it and removes it from the
|
|
145
|
+
result.
|
|
146
|
+
|
|
147
|
+
### `get_arguments() -> dict[str, Any]`
|
|
148
|
+
|
|
149
|
+
Parses `sys.argv` and returns the arguments passed by the user as a dictionary.
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
See [DEVELOPERS.md](DEVELOPERS.md).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
pyarg/__init__.py,sha256=R5_NfeiJii9p9hunfiYdfcHHXBwk2Dx7MN6K7caYlh8,101
|
|
2
|
+
pyarg/parser.py,sha256=vdtXC5DSymuLuuXUMXKy1S-YMIy-2N3ygdaGX1lMPBA,4175
|
|
3
|
+
py_arguments-1.0.1.dist-info/METADATA,sha256=G3pl3-Ns6cnqQABHR2maN_CQ3Z9_-Nm_eQArh1FfY9w,4141
|
|
4
|
+
py_arguments-1.0.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
5
|
+
py_arguments-1.0.1.dist-info/top_level.txt,sha256=y1U_g5JsWcbFiM6lo3f2O9pIKHiJOErDQLFdHIr4tcs,6
|
|
6
|
+
py_arguments-1.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyarg
|
pyarg/__init__.py
ADDED
pyarg/parser.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import re
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Callable, Iterable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Classes.
|
|
9
|
+
class PyArg(ABC, argparse.ArgumentParser):
|
|
10
|
+
"""
|
|
11
|
+
**PyArg** is an abstract class that uses `argparse.ArgumentParser` class that allows you to set up easily sub
|
|
12
|
+
parsers/commands from a parent parser.
|
|
13
|
+
|
|
14
|
+
Creates a class inhenrance from **PyArg**, set up all arguments in `setup_arguments` method, and then, you can
|
|
15
|
+
re-use the same parser definition to build your complex structure of parser arguments and CLI commands.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
```
|
|
19
|
+
class Forms(PyArg):
|
|
20
|
+
def setup_arguments(self):
|
|
21
|
+
self.add_argument("--username", "-u")
|
|
22
|
+
self.add_argument("--password", "-p")
|
|
23
|
+
|
|
24
|
+
parser = Forms(name="Forms")
|
|
25
|
+
arguments = parser.get_arguments()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Extra commands that trigger a function:
|
|
29
|
+
```
|
|
30
|
+
class Application(PyArg):
|
|
31
|
+
def setup_arguments(self):
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
class MakeDir(PyArg):
|
|
35
|
+
@staticmethod
|
|
36
|
+
def create_directory(location, name_list):
|
|
37
|
+
for name in name_list:
|
|
38
|
+
os.makedirs(os.path.join(location, name))
|
|
39
|
+
|
|
40
|
+
def setup_arguments(self):
|
|
41
|
+
self.add_argument("--location", "-l")
|
|
42
|
+
self.add_argument("--name", "-n", type=str, nargs="+")
|
|
43
|
+
|
|
44
|
+
class DeleteDir(PyArg):
|
|
45
|
+
@staticmethod
|
|
46
|
+
def delete_directory(path):
|
|
47
|
+
shutil.rmtree(path)
|
|
48
|
+
|
|
49
|
+
def setup_arguments(self):
|
|
50
|
+
self.add_argument("--path", "-p")
|
|
51
|
+
|
|
52
|
+
make_dir_parser = MakeDir(name="Make Directory")
|
|
53
|
+
delete_dir_parser = DeleteDir(name="Delete Directory")
|
|
54
|
+
|
|
55
|
+
app = Application(name="Directory Manager")
|
|
56
|
+
app.add_command_parser(parser=make_dir_parser, script_entrypoint=MakeDir.create_directory)
|
|
57
|
+
app.add_command_parser(parser=delete_dir_parser, script_entrypoint=DeleteDir.delete_directory)
|
|
58
|
+
app.get_arguments()
|
|
59
|
+
```
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# Special methods.
|
|
63
|
+
def __init__(
|
|
64
|
+
self, name: str, description: str | None = None, parents: Iterable["PyArg"] | None = None, **kwargs: Any
|
|
65
|
+
) -> None:
|
|
66
|
+
# Remove parameters.
|
|
67
|
+
for to_remove in ("prog", "description", "parents", "conflict_handler"):
|
|
68
|
+
kwargs.pop(to_remove, None)
|
|
69
|
+
parents = [] if parents is None else parents
|
|
70
|
+
super().__init__(prog=name, description=description, parents=parents, conflict_handler="resolve", **kwargs)
|
|
71
|
+
|
|
72
|
+
# Attributes.
|
|
73
|
+
self.__command_name: str = self.__get_program_name(name=name)
|
|
74
|
+
|
|
75
|
+
# Set up extra sub parsers.
|
|
76
|
+
self.__subparser: None | argparse._SubParsersAction[argparse.ArgumentParser] = None
|
|
77
|
+
|
|
78
|
+
# Set up arguments.
|
|
79
|
+
self.setup_arguments()
|
|
80
|
+
|
|
81
|
+
# Properties.
|
|
82
|
+
@property
|
|
83
|
+
def command_name(self) -> str:
|
|
84
|
+
return self.__command_name
|
|
85
|
+
|
|
86
|
+
# Abstract methods.
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def setup_arguments(self) -> None:
|
|
89
|
+
"""Main method used to define all parameters of the parser."""
|
|
90
|
+
|
|
91
|
+
# Private methods.
|
|
92
|
+
def __get_program_name(self, name: str) -> str:
|
|
93
|
+
"""Convert the parser name to a CLI command name."""
|
|
94
|
+
name = name.lower()
|
|
95
|
+
name = re.sub(pattern=r"\s+", repl="-", string=name.strip())
|
|
96
|
+
return name
|
|
97
|
+
|
|
98
|
+
# Methods.
|
|
99
|
+
def add_command_parser(
|
|
100
|
+
self, parser: "PyArg", description: str | None = None, script_entrypoint: Callable[..., Any] | None = None
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Add an extra parser to a parent **PyArg** class to be called by `name`."""
|
|
103
|
+
if self.__subparser is None:
|
|
104
|
+
self.__subparser = self.add_subparsers(
|
|
105
|
+
dest="command", parser_class=argparse.ArgumentParser, help="Available commands."
|
|
106
|
+
)
|
|
107
|
+
subparser: argparse.ArgumentParser = self.__subparser.add_parser(
|
|
108
|
+
parents=[parser], name=parser.command_name, description=description or parser.description, add_help=False
|
|
109
|
+
)
|
|
110
|
+
# Add script.
|
|
111
|
+
if script_entrypoint:
|
|
112
|
+
subparser.set_defaults(_entrypoint=script_entrypoint)
|
|
113
|
+
|
|
114
|
+
def get_arguments(self) -> dict[str, Any]:
|
|
115
|
+
"""Get the CLI arguments."""
|
|
116
|
+
args: dict[str, Any] = vars(self.parse_args())
|
|
117
|
+
args.pop("_entrypoint", None)
|
|
118
|
+
return args
|