magicli 2.1.2__tar.gz → 2.1.3__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.
- {magicli-2.1.2 → magicli-2.1.3}/PKG-INFO +1 -1
- {magicli-2.1.2 → magicli-2.1.3}/magicli.egg-info/PKG-INFO +1 -1
- {magicli-2.1.2 → magicli-2.1.3}/magicli.py +36 -12
- {magicli-2.1.2 → magicli-2.1.3}/tests/test_magicli.py +5 -3
- {magicli-2.1.2 → magicli-2.1.3}/tests/test_parse_kwarg.py +35 -3
- {magicli-2.1.2 → magicli-2.1.3}/tests/test_parse_short_options.py +11 -10
- {magicli-2.1.2 → magicli-2.1.3}/.github/workflows/lint.yml +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/.github/workflows/pytest.yml +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/.github/workflows/release.yml +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/.gitignore +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/LICENSE +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/README.md +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/magicli.egg-info/SOURCES.txt +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/magicli.egg-info/dependency_links.txt +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/magicli.egg-info/entry_points.txt +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/magicli.egg-info/requires.txt +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/magicli.egg-info/top_level.txt +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/pyproject.toml +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/setup.cfg +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/tests/fixtures.py +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/tests/test_cli.py +0 -0
- {magicli-2.1.2 → magicli-2.1.3}/tests/test_help.py +0 -0
|
@@ -73,8 +73,10 @@ def call(function, argv, module=None, name=None):
|
|
|
73
73
|
|
|
74
74
|
try:
|
|
75
75
|
args, kwargs = parse_argv(argv, parameters, docstring)
|
|
76
|
-
except ParseArgvError:
|
|
77
|
-
|
|
76
|
+
except ParseArgvError as exc:
|
|
77
|
+
message = exc.args[0] + "\n\n" if exc.args else ""
|
|
78
|
+
message += help_message(help_from_function, function, name)
|
|
79
|
+
raise SystemExit(message)
|
|
78
80
|
|
|
79
81
|
function(*args, **kwargs)
|
|
80
82
|
|
|
@@ -92,8 +94,8 @@ def parse_argv(argv, parameters, docstring):
|
|
|
92
94
|
parse_short_options(key[1:], docstring, iter_argv, parameters, kwargs)
|
|
93
95
|
else:
|
|
94
96
|
if (index := len(args)) >= len(parameter_list):
|
|
95
|
-
raise ParseArgvError
|
|
96
|
-
args.append(get_type(parameter_list[index])
|
|
97
|
+
raise ParseArgvError(f"{key}: unknown command")
|
|
98
|
+
args.append(cast_value(key, get_type(parameter_list[index])))
|
|
97
99
|
|
|
98
100
|
check_all_args_present(len(args), parameter_list)
|
|
99
101
|
|
|
@@ -108,7 +110,9 @@ def check_all_args_present(len_args, parameter_list):
|
|
|
108
110
|
if len_args < len(parameter_list):
|
|
109
111
|
parameter = parameter_list[len_args]
|
|
110
112
|
if parameter.default is parameter.empty:
|
|
111
|
-
raise ParseArgvError
|
|
113
|
+
raise ParseArgvError(
|
|
114
|
+
f"{parameter_list[len_args].name}: positional argument missing"
|
|
115
|
+
)
|
|
112
116
|
|
|
113
117
|
|
|
114
118
|
def parse_kwarg(key, argv, parameters):
|
|
@@ -119,16 +123,36 @@ def parse_kwarg(key, argv, parameters):
|
|
|
119
123
|
"""
|
|
120
124
|
key, value = key.split("=", 1) if "=" in key else (key, None)
|
|
121
125
|
key = key.replace("-", "_")
|
|
122
|
-
|
|
126
|
+
|
|
127
|
+
if key not in parameters:
|
|
128
|
+
raise ParseArgvError(f"--{key}: unknown long option")
|
|
129
|
+
|
|
130
|
+
cast_to = get_type(parameters[key])
|
|
123
131
|
|
|
124
132
|
if value is None:
|
|
125
133
|
if cast_to is bool:
|
|
126
134
|
return key, not parameters[key].default
|
|
127
135
|
if cast_to is type(None):
|
|
128
136
|
return key, True
|
|
129
|
-
value =
|
|
137
|
+
value = next_arg(argv)
|
|
138
|
+
|
|
139
|
+
return key, cast_value(value, cast_to)
|
|
140
|
+
|
|
130
141
|
|
|
131
|
-
|
|
142
|
+
def next_arg(argv):
|
|
143
|
+
"""Return the next command-line argument or raise a parser error."""
|
|
144
|
+
try:
|
|
145
|
+
return next(argv)
|
|
146
|
+
except StopIteration:
|
|
147
|
+
raise ParseArgvError("error: missing option value")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def cast_value(value, cast_to):
|
|
151
|
+
"""Cast a command-line argument value or raise a parser error."""
|
|
152
|
+
try:
|
|
153
|
+
return value if cast_to is str else cast_to(value)
|
|
154
|
+
except ValueError as exc:
|
|
155
|
+
raise ParseArgvError(exc.args[0]) if exc.args else ParseArgvError from exc
|
|
132
156
|
|
|
133
157
|
|
|
134
158
|
def parse_short_options(short_options, docstring, iter_argv, parameters, kwargs):
|
|
@@ -137,7 +161,7 @@ def parse_short_options(short_options, docstring, iter_argv, parameters, kwargs)
|
|
|
137
161
|
long = short_to_long_option(short, docstring)
|
|
138
162
|
|
|
139
163
|
if long not in parameters:
|
|
140
|
-
raise
|
|
164
|
+
raise ParseArgvError(f"--{long}: invalid long option")
|
|
141
165
|
|
|
142
166
|
cast_to = get_type(parameters[long])
|
|
143
167
|
|
|
@@ -146,9 +170,9 @@ def parse_short_options(short_options, docstring, iter_argv, parameters, kwargs)
|
|
|
146
170
|
elif cast_to is type(None):
|
|
147
171
|
kwargs[long] = True
|
|
148
172
|
elif i == len(short_options) - 1:
|
|
149
|
-
kwargs[long] =
|
|
173
|
+
kwargs[long] = cast_value(next_arg(iter_argv), cast_to)
|
|
150
174
|
else:
|
|
151
|
-
raise
|
|
175
|
+
raise ParseArgvError(f"-{short}: invalid type")
|
|
152
176
|
|
|
153
177
|
|
|
154
178
|
def short_to_long_option(short, docstring):
|
|
@@ -160,7 +184,7 @@ def short_to_long_option(short, docstring):
|
|
|
160
184
|
chars = [" ", "\n", "]"]
|
|
161
185
|
indices = (i for char in chars if (i := docstring.find(char, start)) != -1)
|
|
162
186
|
return docstring[start : min(indices, default=None)]
|
|
163
|
-
raise
|
|
187
|
+
raise ParseArgvError(f"-{short}: invalid short option")
|
|
164
188
|
|
|
165
189
|
|
|
166
190
|
def get_type(parameter):
|
|
@@ -59,8 +59,9 @@ def test_command_called(mocked, caplog):
|
|
|
59
59
|
@mock.patch("importlib.import_module", side_effect=module)
|
|
60
60
|
def test_wrong_command_not_called(mocked):
|
|
61
61
|
sys.argv = ["name", "wrong_command"]
|
|
62
|
-
with pytest.raises(SystemExit):
|
|
62
|
+
with pytest.raises(SystemExit) as error:
|
|
63
63
|
magicli()
|
|
64
|
+
assert error.value.code.startswith("wrong_command: unknown command")
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
@mock.patch("importlib.import_module", side_effect=module_empty)
|
|
@@ -87,8 +88,9 @@ def test_module_is_magicli(pyproject):
|
|
|
87
88
|
@mock.patch("importlib.import_module", side_effect=module)
|
|
88
89
|
def test_short_option_with_wrong_type(mocked):
|
|
89
90
|
sys.argv = ["name", "-ab"]
|
|
90
|
-
with pytest.raises(SystemExit):
|
|
91
|
+
with pytest.raises(SystemExit) as error:
|
|
91
92
|
magicli()
|
|
93
|
+
assert error.value.code.startswith("-a: invalid short option")
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
@mock.patch("importlib.import_module", side_effect=module_version)
|
|
@@ -103,7 +105,7 @@ def test_version(mocked, caplog):
|
|
|
103
105
|
sys.argv = ["name", "-v"]
|
|
104
106
|
with pytest.raises(SystemExit) as error:
|
|
105
107
|
magicli()
|
|
106
|
-
assert error.value.code
|
|
108
|
+
assert error.value.code.startswith("-v: invalid short option")
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
@mock.patch("importlib.import_module", side_effect=module_version)
|
|
@@ -3,7 +3,7 @@ from inspect import Parameter, _ParameterKind
|
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
|
-
from magicli import get_type, parse_argv, parse_kwarg
|
|
6
|
+
from magicli import ParseArgvError, get_type, parse_argv, parse_kwarg
|
|
7
7
|
|
|
8
8
|
PK = _ParameterKind.POSITIONAL_OR_KEYWORD
|
|
9
9
|
|
|
@@ -44,8 +44,6 @@ def test_parse_argv():
|
|
|
44
44
|
["a"],
|
|
45
45
|
{"kwarg": 2},
|
|
46
46
|
)
|
|
47
|
-
with pytest.raises(ParseArgvError):
|
|
48
|
-
parse_argv([], parameters, docstring="")
|
|
49
47
|
|
|
50
48
|
|
|
51
49
|
def test_parse_argv_with_underscore():
|
|
@@ -58,3 +56,37 @@ def test_parse_argv_with_underscore():
|
|
|
58
56
|
["a"],
|
|
59
57
|
{"kwarg_1": 2},
|
|
60
58
|
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize(
|
|
62
|
+
("command", "error_message"),
|
|
63
|
+
[
|
|
64
|
+
(["a", "--unknown=2"], "--unknown: unknown long option"),
|
|
65
|
+
(["a", "--kwarg"], "error: missing option value"),
|
|
66
|
+
([], "arg: positional argument missing"),
|
|
67
|
+
],
|
|
68
|
+
)
|
|
69
|
+
def test_parse_argv_errors(command, error_message):
|
|
70
|
+
parameters = inspect.signature(lambda arg, kwarg=1: None).parameters
|
|
71
|
+
with pytest.raises(ParseArgvError) as error:
|
|
72
|
+
parse_argv(command, parameters, docstring="")
|
|
73
|
+
assert error.value.args[0] == error_message
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.parametrize(
|
|
77
|
+
("command", "result"),
|
|
78
|
+
[
|
|
79
|
+
(["--kwarg", "''"], "''"),
|
|
80
|
+
(["--kwarg="], ""),
|
|
81
|
+
],
|
|
82
|
+
)
|
|
83
|
+
def test_parse_argv_empty_kwarg(command, result):
|
|
84
|
+
parameters = inspect.signature(lambda kwarg="1": None).parameters
|
|
85
|
+
res = parse_argv(command, parameters, docstring="")
|
|
86
|
+
assert res == ([], {"kwarg": result})
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_parse_argv_with_invalid_type_raises_parse_error():
|
|
90
|
+
with pytest.raises(ParseArgvError) as error:
|
|
91
|
+
parse_argv(["not-an-int"], {"arg": Parameter("arg", PK, annotation=int)}, "")
|
|
92
|
+
assert error.value.args[0] == "invalid literal for int() with base 10: 'not-an-int'"
|
|
@@ -2,7 +2,7 @@ from inspect import Parameter, _ParameterKind
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from magicli import parse_short_options, short_to_long_option
|
|
5
|
+
from magicli import ParseArgvError, parse_short_options, short_to_long_option
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@pytest.mark.parametrize(
|
|
@@ -29,21 +29,21 @@ def test_parse_short_options(default, result):
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def test_parse_short_options_failures():
|
|
32
|
-
|
|
32
|
+
kwargs = {
|
|
33
33
|
"short_options": "a",
|
|
34
34
|
"docstring": "-a, --abc",
|
|
35
35
|
"iter_argv": iter(["b"]),
|
|
36
36
|
"parameters": {"abc": Parameter("abc", _ParameterKind.KEYWORD_ONLY)},
|
|
37
37
|
"kwargs": {},
|
|
38
38
|
}
|
|
39
|
-
for args in [
|
|
40
|
-
{"parameters": {}},
|
|
41
|
-
{"docstring": ""},
|
|
42
|
-
{"short_options": "aa", "iter_argv": iter(["aa"])},
|
|
39
|
+
for args, err in [
|
|
40
|
+
({"parameters": {}}, ("--abc: invalid long option",)),
|
|
41
|
+
({"docstring": ""}, ("-a: invalid short option",)),
|
|
42
|
+
({"short_options": "aa", "iter_argv": iter(["aa"])}, ("-a: invalid type",)),
|
|
43
43
|
]:
|
|
44
|
-
with pytest.raises(
|
|
45
|
-
parse_short_options(**(
|
|
46
|
-
|
|
44
|
+
with pytest.raises(ParseArgvError) as error:
|
|
45
|
+
parse_short_options(**(kwargs | args))
|
|
46
|
+
assert error.value.args == err
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@pytest.mark.parametrize(
|
|
@@ -68,5 +68,6 @@ def test_short_to_long_option(docstring):
|
|
|
68
68
|
],
|
|
69
69
|
)
|
|
70
70
|
def test_short_to_long_option_failures(docstring):
|
|
71
|
-
with pytest.raises(
|
|
71
|
+
with pytest.raises(ParseArgvError) as error:
|
|
72
72
|
short_to_long_option("a", docstring)
|
|
73
|
+
assert error.value.args[0] == "-a: invalid short option"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|