edq-utils 0.1.9__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.
- edq/__init__.py +5 -0
- edq/cli/__init__.py +0 -0
- edq/cli/config/__init__.py +3 -0
- edq/cli/config/list.py +69 -0
- edq/cli/http/__init__.py +3 -0
- edq/cli/http/exchange-server.py +71 -0
- edq/cli/http/send-exchange.py +45 -0
- edq/cli/http/verify-exchanges.py +38 -0
- edq/cli/testing/__init__.py +3 -0
- edq/cli/testing/cli-test.py +49 -0
- edq/cli/version.py +28 -0
- edq/core/__init__.py +0 -0
- edq/core/argparser.py +137 -0
- edq/core/argparser_test.py +124 -0
- edq/core/config.py +268 -0
- edq/core/config_test.py +1038 -0
- edq/core/log.py +101 -0
- edq/core/version.py +6 -0
- edq/procedure/__init__.py +0 -0
- edq/procedure/verify_exchanges.py +85 -0
- edq/py.typed +0 -0
- edq/testing/__init__.py +3 -0
- edq/testing/asserts.py +65 -0
- edq/testing/cli.py +360 -0
- edq/testing/cli_test.py +15 -0
- edq/testing/httpserver.py +578 -0
- edq/testing/httpserver_test.py +424 -0
- edq/testing/run.py +142 -0
- edq/testing/testdata/cli/data/configs/empty/edq-config.json +1 -0
- edq/testing/testdata/cli/data/configs/simple-1/edq-config.json +4 -0
- edq/testing/testdata/cli/data/configs/simple-2/edq-config.json +3 -0
- edq/testing/testdata/cli/data/configs/value-number/edq-config.json +3 -0
- edq/testing/testdata/cli/tests/config/list/config_list_base.txt +16 -0
- edq/testing/testdata/cli/tests/config/list/config_list_config_value_number.txt +10 -0
- edq/testing/testdata/cli/tests/config/list/config_list_ignore_config.txt +14 -0
- edq/testing/testdata/cli/tests/config/list/config_list_no_config.txt +8 -0
- edq/testing/testdata/cli/tests/config/list/config_list_show_origin.txt +13 -0
- edq/testing/testdata/cli/tests/config/list/config_list_skip_header.txt +10 -0
- edq/testing/testdata/cli/tests/help_base.txt +9 -0
- edq/testing/testdata/cli/tests/platform_skip.txt +5 -0
- edq/testing/testdata/cli/tests/version_base.txt +6 -0
- edq/testing/testdata/http/exchanges/simple.httpex.json +5 -0
- edq/testing/testdata/http/exchanges/simple_anchor.httpex.json +5 -0
- edq/testing/testdata/http/exchanges/simple_file.httpex.json +10 -0
- edq/testing/testdata/http/exchanges/simple_file_binary.httpex.json +10 -0
- edq/testing/testdata/http/exchanges/simple_file_get_params.httpex.json +14 -0
- edq/testing/testdata/http/exchanges/simple_file_multiple.httpex.json +13 -0
- edq/testing/testdata/http/exchanges/simple_file_name.httpex.json +11 -0
- edq/testing/testdata/http/exchanges/simple_file_post_multiple.httpex.json +13 -0
- edq/testing/testdata/http/exchanges/simple_file_post_params.httpex.json +14 -0
- edq/testing/testdata/http/exchanges/simple_headers.httpex.json +8 -0
- edq/testing/testdata/http/exchanges/simple_jsonresponse_dict.httpex.json +7 -0
- edq/testing/testdata/http/exchanges/simple_jsonresponse_list.httpex.json +9 -0
- edq/testing/testdata/http/exchanges/simple_params.httpex.json +9 -0
- edq/testing/testdata/http/exchanges/simple_post.httpex.json +5 -0
- edq/testing/testdata/http/exchanges/simple_post_params.httpex.json +9 -0
- edq/testing/testdata/http/exchanges/simple_post_urlparams.httpex.json +5 -0
- edq/testing/testdata/http/exchanges/simple_urlparams.httpex.json +5 -0
- edq/testing/testdata/http/exchanges/specialcase_listparams_explicit.httpex.json +8 -0
- edq/testing/testdata/http/exchanges/specialcase_listparams_url.httpex.json +5 -0
- edq/testing/testdata/http/files/a.txt +1 -0
- edq/testing/testdata/http/files/tiny.png +0 -0
- edq/testing/unittest.py +88 -0
- edq/util/__init__.py +3 -0
- edq/util/dirent.py +340 -0
- edq/util/dirent_test.py +979 -0
- edq/util/encoding.py +18 -0
- edq/util/hash.py +41 -0
- edq/util/hash_test.py +89 -0
- edq/util/json.py +180 -0
- edq/util/json_test.py +228 -0
- edq/util/net.py +1008 -0
- edq/util/parse.py +33 -0
- edq/util/pyimport.py +94 -0
- edq/util/pyimport_test.py +119 -0
- edq/util/reflection.py +32 -0
- edq/util/time.py +75 -0
- edq/util/time_test.py +107 -0
- edq_utils-0.1.9.dist-info/METADATA +164 -0
- edq_utils-0.1.9.dist-info/RECORD +83 -0
- edq_utils-0.1.9.dist-info/WHEEL +5 -0
- edq_utils-0.1.9.dist-info/licenses/LICENSE +21 -0
- edq_utils-0.1.9.dist-info/top_level.txt +1 -0
edq/__init__.py
ADDED
edq/cli/__init__.py
ADDED
|
File without changes
|
edq/cli/config/list.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
List the current configuration options.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import edq.core.argparser
|
|
9
|
+
|
|
10
|
+
CONFIG_FIELD_SEPARATOR: str = "\t"
|
|
11
|
+
|
|
12
|
+
def run_cli(args: argparse.Namespace) -> int:
|
|
13
|
+
""" Run the CLI. """
|
|
14
|
+
|
|
15
|
+
rows = []
|
|
16
|
+
|
|
17
|
+
for (key, value) in args._config.items():
|
|
18
|
+
row = [key, str(value)]
|
|
19
|
+
if (args.show_origin):
|
|
20
|
+
config_source_obj = args._config_sources.get(key)
|
|
21
|
+
|
|
22
|
+
origin = config_source_obj.path
|
|
23
|
+
if (origin is None):
|
|
24
|
+
origin = config_source_obj.label
|
|
25
|
+
|
|
26
|
+
row.append(origin)
|
|
27
|
+
|
|
28
|
+
rows.append(CONFIG_FIELD_SEPARATOR.join(row))
|
|
29
|
+
|
|
30
|
+
rows.sort()
|
|
31
|
+
|
|
32
|
+
if (not args.skip_header):
|
|
33
|
+
header = ["Key", "Value"]
|
|
34
|
+
if (args.show_origin):
|
|
35
|
+
header.append("Origin")
|
|
36
|
+
|
|
37
|
+
rows.insert(0, (CONFIG_FIELD_SEPARATOR.join(header)))
|
|
38
|
+
|
|
39
|
+
print("\n".join(rows))
|
|
40
|
+
return 0
|
|
41
|
+
|
|
42
|
+
def main() -> int:
|
|
43
|
+
""" Get a parser, parse the args, and call run. """
|
|
44
|
+
|
|
45
|
+
return run_cli(_get_parser().parse_args())
|
|
46
|
+
|
|
47
|
+
def _get_parser() -> argparse.ArgumentParser:
|
|
48
|
+
""" Get a parser and add addition flags. """
|
|
49
|
+
|
|
50
|
+
parser = edq.core.argparser.get_default_parser(__doc__.strip())
|
|
51
|
+
modify_parser(parser)
|
|
52
|
+
|
|
53
|
+
return parser
|
|
54
|
+
|
|
55
|
+
def modify_parser(parser: argparse.ArgumentParser) -> None:
|
|
56
|
+
""" Add this CLI's flags to the given parser. """
|
|
57
|
+
|
|
58
|
+
parser.add_argument("--show-origin", dest = 'show_origin',
|
|
59
|
+
action = 'store_true',
|
|
60
|
+
help = "Display where each configuration's value was obtained from.",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
parser.add_argument("--skip-header", dest = 'skip_header',
|
|
64
|
+
action = 'store_true',
|
|
65
|
+
help = 'Skip headers when displaying configs.',
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if (__name__ == '__main__'):
|
|
69
|
+
sys.exit(main())
|
edq/cli/http/__init__.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# pylint: disable=invalid-name
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Start an HTTP test server that serves the specified HTTP exchanges.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import edq.core.argparser
|
|
12
|
+
import edq.testing.httpserver
|
|
13
|
+
|
|
14
|
+
def run_cli(args: argparse.Namespace) -> int:
|
|
15
|
+
""" Run the CLI. """
|
|
16
|
+
|
|
17
|
+
match_options = {
|
|
18
|
+
'params_to_skip': args.ignore_params,
|
|
19
|
+
'headers_to_skip': args.ignore_headers,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
server = edq.testing.httpserver.HTTPTestServer(
|
|
23
|
+
port = args.port,
|
|
24
|
+
match_options = match_options,
|
|
25
|
+
verbose = True,
|
|
26
|
+
raise_on_404 = False,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
for path in args.paths:
|
|
30
|
+
path = os.path.abspath(path)
|
|
31
|
+
|
|
32
|
+
if (os.path.isfile(path)):
|
|
33
|
+
server.load_exchange(path)
|
|
34
|
+
else:
|
|
35
|
+
server.load_exchanges_dir(path)
|
|
36
|
+
|
|
37
|
+
server.start_and_wait()
|
|
38
|
+
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
def main() -> int:
|
|
42
|
+
""" Get a parser, parse the args, and call run. """
|
|
43
|
+
return run_cli(_get_parser().parse_args())
|
|
44
|
+
|
|
45
|
+
def _get_parser() -> argparse.ArgumentParser:
|
|
46
|
+
""" Get the parser. """
|
|
47
|
+
|
|
48
|
+
parser = edq.core.argparser.get_default_parser(__doc__.strip(),
|
|
49
|
+
include_net = True,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
parser.add_argument('paths', metavar = 'PATH',
|
|
53
|
+
type = str, nargs = '+',
|
|
54
|
+
help = 'Path to exchange files or dirs (which will be recursively searched for all exchange files).')
|
|
55
|
+
|
|
56
|
+
parser.add_argument('--port', dest = 'port',
|
|
57
|
+
action = 'store', type = int, default = None,
|
|
58
|
+
help = 'The port to run this test server on. If not set, a random open port will be chosen.')
|
|
59
|
+
|
|
60
|
+
parser.add_argument('--ignore-param', dest = 'ignore_params',
|
|
61
|
+
action = 'append', type = str, default = [],
|
|
62
|
+
help = 'Ignore this parameter during exchange matching.')
|
|
63
|
+
|
|
64
|
+
parser.add_argument('--ignore-header', dest = 'ignore_headers',
|
|
65
|
+
action = 'append', type = str, default = [],
|
|
66
|
+
help = 'Ignore this header during exchange matching.')
|
|
67
|
+
|
|
68
|
+
return parser
|
|
69
|
+
|
|
70
|
+
if (__name__ == '__main__'):
|
|
71
|
+
sys.exit(main())
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# pylint: disable=invalid-name
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Send an HTTP exchange to the target server.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import edq.core.argparser
|
|
11
|
+
import edq.util.net
|
|
12
|
+
|
|
13
|
+
def run_cli(args: argparse.Namespace) -> int:
|
|
14
|
+
""" Run the CLI. """
|
|
15
|
+
|
|
16
|
+
exchange = edq.util.net.HTTPExchange.from_path(args.path)
|
|
17
|
+
_, body = exchange.make_request(args.server)
|
|
18
|
+
|
|
19
|
+
print(body)
|
|
20
|
+
|
|
21
|
+
return 0
|
|
22
|
+
|
|
23
|
+
def main() -> int:
|
|
24
|
+
""" Get a parser, parse the args, and call run. """
|
|
25
|
+
return run_cli(_get_parser().parse_args())
|
|
26
|
+
|
|
27
|
+
def _get_parser() -> argparse.ArgumentParser:
|
|
28
|
+
""" Get the parser. """
|
|
29
|
+
|
|
30
|
+
parser = edq.core.argparser.get_default_parser(__doc__.strip(),
|
|
31
|
+
include_net = True,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
parser.add_argument('server', metavar = 'SERVER',
|
|
35
|
+
action = 'store', type = str,
|
|
36
|
+
help = 'Server to send the exahnge to.')
|
|
37
|
+
|
|
38
|
+
parser.add_argument('path', metavar = 'PATH',
|
|
39
|
+
action = 'store', type = str,
|
|
40
|
+
help = 'Path to the exchange file.')
|
|
41
|
+
|
|
42
|
+
return parser
|
|
43
|
+
|
|
44
|
+
if (__name__ == '__main__'):
|
|
45
|
+
sys.exit(main())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# pylint: disable=invalid-name
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Verify that exchanges sent to a given server have the same response.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import edq.core.argparser
|
|
11
|
+
import edq.procedure.verify_exchanges
|
|
12
|
+
|
|
13
|
+
def run_cli(args: argparse.Namespace) -> int:
|
|
14
|
+
""" Run the CLI. """
|
|
15
|
+
|
|
16
|
+
return edq.procedure.verify_exchanges.run(args.paths, args.server)
|
|
17
|
+
|
|
18
|
+
def main() -> int:
|
|
19
|
+
""" Get a parser, parse the args, and call run. """
|
|
20
|
+
return run_cli(_get_parser().parse_args())
|
|
21
|
+
|
|
22
|
+
def _get_parser() -> argparse.ArgumentParser:
|
|
23
|
+
""" Get the parser. """
|
|
24
|
+
|
|
25
|
+
parser = edq.core.argparser.get_default_parser(__doc__.strip())
|
|
26
|
+
|
|
27
|
+
parser.add_argument('server', metavar = 'SERVER',
|
|
28
|
+
action = 'store', type = str, default = None,
|
|
29
|
+
help = 'Address of the server to send exchanges to.')
|
|
30
|
+
|
|
31
|
+
parser.add_argument('paths', metavar = 'PATH',
|
|
32
|
+
type = str, nargs = '+',
|
|
33
|
+
help = 'Path to exchange files or dirs (which will be recursively searched for all exchange files).')
|
|
34
|
+
|
|
35
|
+
return parser
|
|
36
|
+
|
|
37
|
+
if (__name__ == '__main__'):
|
|
38
|
+
sys.exit(main())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# pylint: disable=invalid-name
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Run specified CLI test files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
import unittest
|
|
10
|
+
|
|
11
|
+
import edq.core.argparser
|
|
12
|
+
import edq.testing.cli
|
|
13
|
+
import edq.testing.unittest
|
|
14
|
+
|
|
15
|
+
class CLITest(edq.testing.unittest.BaseTest):
|
|
16
|
+
""" Test CLI invocations. """
|
|
17
|
+
|
|
18
|
+
def run_cli(args: argparse.Namespace) -> int:
|
|
19
|
+
""" Run the CLI. """
|
|
20
|
+
|
|
21
|
+
edq.testing.cli.add_test_paths(CLITest, args.data_dir, args.paths)
|
|
22
|
+
|
|
23
|
+
runner = unittest.TextTestRunner(verbosity = 2)
|
|
24
|
+
tests = unittest.defaultTestLoader.loadTestsFromTestCase(CLITest)
|
|
25
|
+
results = runner.run(tests)
|
|
26
|
+
|
|
27
|
+
return len(results.errors) + len(results.failures)
|
|
28
|
+
|
|
29
|
+
def main() -> int:
|
|
30
|
+
""" Get a parser, parse the args, and call run. """
|
|
31
|
+
return run_cli(_get_parser().parse_args())
|
|
32
|
+
|
|
33
|
+
def _get_parser() -> argparse.ArgumentParser:
|
|
34
|
+
""" Get the parser. """
|
|
35
|
+
|
|
36
|
+
parser = edq.core.argparser.get_default_parser(__doc__.strip())
|
|
37
|
+
|
|
38
|
+
parser.add_argument('paths', metavar = 'PATH',
|
|
39
|
+
type = str, nargs = '+',
|
|
40
|
+
help = 'Path to CLI test case files.')
|
|
41
|
+
|
|
42
|
+
parser.add_argument('--data-dir', dest = 'data_dir',
|
|
43
|
+
action = 'store', type = str, default = '.',
|
|
44
|
+
help = 'The additional data directory (expansion of __DATA_DIR__) used for tests (default: %(default)s).')
|
|
45
|
+
|
|
46
|
+
return parser
|
|
47
|
+
|
|
48
|
+
if (__name__ == '__main__'):
|
|
49
|
+
sys.exit(main())
|
edq/cli/version.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Get the version of the EduLinq Python utils package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import edq.core.argparser
|
|
9
|
+
import edq.core.version
|
|
10
|
+
|
|
11
|
+
def run_cli(args: argparse.Namespace) -> int:
|
|
12
|
+
""" Run the CLI. """
|
|
13
|
+
|
|
14
|
+
print(f"v{edq.core.version.get_version()}")
|
|
15
|
+
return 0
|
|
16
|
+
|
|
17
|
+
def main() -> int:
|
|
18
|
+
""" Get a parser, parse the args, and call run. """
|
|
19
|
+
|
|
20
|
+
return run_cli(_get_parser().parse_args())
|
|
21
|
+
|
|
22
|
+
def _get_parser() -> argparse.ArgumentParser:
|
|
23
|
+
""" Get the parser. """
|
|
24
|
+
|
|
25
|
+
return edq.core.argparser.get_default_parser(__doc__.strip())
|
|
26
|
+
|
|
27
|
+
if (__name__ == '__main__'):
|
|
28
|
+
sys.exit(main())
|
edq/core/__init__.py
ADDED
|
File without changes
|
edq/core/argparser.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A place to handle common CLI arguments.
|
|
3
|
+
"parsers" in this file are always assumed to be argparse parsers.
|
|
4
|
+
|
|
5
|
+
The general idea is that callers can register callbacks to be called before and after parsing CLI arguments.
|
|
6
|
+
Pre-callbacks are generally intended to add arguments to the parser,
|
|
7
|
+
while post-callbacks are generally intended to act on the results of parsing.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import functools
|
|
12
|
+
import typing
|
|
13
|
+
|
|
14
|
+
import edq.core.config
|
|
15
|
+
import edq.core.log
|
|
16
|
+
import edq.util.net
|
|
17
|
+
|
|
18
|
+
@typing.runtime_checkable
|
|
19
|
+
class PreParseFunction(typing.Protocol):
|
|
20
|
+
"""
|
|
21
|
+
A function that can be called before parsing arguments.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __call__(self, parser: argparse.ArgumentParser, extra_state: typing.Dict[str, typing.Any]) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Prepare a parser for parsing.
|
|
27
|
+
This is generally used for adding your module's arguments to the parser,
|
|
28
|
+
for example a logging module may add arguments to set a logging level.
|
|
29
|
+
|
|
30
|
+
The extra state is shared between all pre-parse functions
|
|
31
|
+
and will be placed in the final parsed output under `_pre_extra_state_`.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@typing.runtime_checkable
|
|
35
|
+
class PostParseFunction(typing.Protocol):
|
|
36
|
+
"""
|
|
37
|
+
A function that can be called after parsing arguments.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __call__(self,
|
|
41
|
+
parser: argparse.ArgumentParser,
|
|
42
|
+
args: argparse.Namespace,
|
|
43
|
+
extra_state: typing.Dict[str, typing.Any]) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Take actions after arguments are parsed.
|
|
46
|
+
This is generally used for initializing your module with options,
|
|
47
|
+
for example a logging module may set a logging level.
|
|
48
|
+
|
|
49
|
+
The extra state is shared between all post-parse functions
|
|
50
|
+
and will be placed in the final parsed output under `_post_extra_state_`.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
class Parser(argparse.ArgumentParser):
|
|
54
|
+
"""
|
|
55
|
+
Extend an argparse parser to call the pre and post functions.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
59
|
+
super().__init__(*args, **kwargs)
|
|
60
|
+
|
|
61
|
+
self._pre_parse_callbacks: typing.Dict[str, PreParseFunction] = {}
|
|
62
|
+
self._post_parse_callbacks: typing.Dict[str, PostParseFunction] = {}
|
|
63
|
+
|
|
64
|
+
def register_callbacks(self,
|
|
65
|
+
key: str,
|
|
66
|
+
pre_parse_callback: typing.Union[PreParseFunction, None] = None,
|
|
67
|
+
post_parse_callback: typing.Union[PostParseFunction, None] = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Register callback functions to run before/after argument parsing.
|
|
71
|
+
Any existing callbacks under the specified key will be replaced.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
if (pre_parse_callback is not None):
|
|
75
|
+
self._pre_parse_callbacks[key] = pre_parse_callback
|
|
76
|
+
|
|
77
|
+
if (post_parse_callback is not None):
|
|
78
|
+
self._post_parse_callbacks[key] = post_parse_callback
|
|
79
|
+
|
|
80
|
+
def parse_args(self, # type: ignore[override]
|
|
81
|
+
*args: typing.Any,
|
|
82
|
+
skip_keys: typing.Union[typing.List[str], None] = None,
|
|
83
|
+
**kwargs: typing.Any) -> argparse.Namespace:
|
|
84
|
+
if (skip_keys is None):
|
|
85
|
+
skip_keys = []
|
|
86
|
+
|
|
87
|
+
# Call pre-parse callbacks.
|
|
88
|
+
pre_extra_state: typing.Dict[str, typing.Any] = {}
|
|
89
|
+
for (key, pre_parse_callback) in self._pre_parse_callbacks.items():
|
|
90
|
+
if (key not in skip_keys):
|
|
91
|
+
pre_parse_callback(self, pre_extra_state)
|
|
92
|
+
|
|
93
|
+
# Parse the args.
|
|
94
|
+
parsed_args = super().parse_args(*args, **kwargs)
|
|
95
|
+
|
|
96
|
+
# Call post-parse callbacks.
|
|
97
|
+
post_extra_state: typing.Dict[str, typing.Any] = {}
|
|
98
|
+
for (key, post_parse_callback) in self._post_parse_callbacks.items():
|
|
99
|
+
if (key not in skip_keys):
|
|
100
|
+
post_parse_callback(self, parsed_args, post_extra_state)
|
|
101
|
+
|
|
102
|
+
# Attach the additional state to the args.
|
|
103
|
+
setattr(parsed_args, '_pre_extra_state_', pre_extra_state)
|
|
104
|
+
setattr(parsed_args, '_post_extra_state_', post_extra_state)
|
|
105
|
+
|
|
106
|
+
return parsed_args # type: ignore[no-any-return]
|
|
107
|
+
|
|
108
|
+
def get_default_parser(description: str,
|
|
109
|
+
version: typing.Union[str, None] = None,
|
|
110
|
+
include_log: bool = True,
|
|
111
|
+
include_config: bool = True,
|
|
112
|
+
include_net: bool = False,
|
|
113
|
+
config_options: typing.Union[typing.Dict[str, typing.Any], None] = None,
|
|
114
|
+
) -> Parser:
|
|
115
|
+
""" Get a parser with the requested default callbacks already attached. """
|
|
116
|
+
|
|
117
|
+
if (config_options is None):
|
|
118
|
+
config_options = {}
|
|
119
|
+
|
|
120
|
+
parser = Parser(description = description)
|
|
121
|
+
|
|
122
|
+
if (version is not None):
|
|
123
|
+
parser.add_argument('--version',
|
|
124
|
+
action = 'version', version = version)
|
|
125
|
+
|
|
126
|
+
if (include_log):
|
|
127
|
+
parser.register_callbacks('log', edq.core.log.set_cli_args, edq.core.log.init_from_args)
|
|
128
|
+
|
|
129
|
+
if (include_config):
|
|
130
|
+
config_pre_func = functools.partial(edq.core.config.set_cli_args, **config_options)
|
|
131
|
+
config_post_func = functools.partial(edq.core.config.load_config_into_args, **config_options)
|
|
132
|
+
parser.register_callbacks('config', config_pre_func, config_post_func)
|
|
133
|
+
|
|
134
|
+
if (include_net):
|
|
135
|
+
parser.register_callbacks('net', edq.util.net.set_cli_args, edq.util.net.init_from_args)
|
|
136
|
+
|
|
137
|
+
return parser
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
import edq.core.argparser
|
|
4
|
+
import edq.testing.unittest
|
|
5
|
+
|
|
6
|
+
class TestArgParser(edq.testing.unittest.BaseTest):
|
|
7
|
+
""" Test argument parsing. """
|
|
8
|
+
|
|
9
|
+
def test_callbacks_base(self):
|
|
10
|
+
""" Test the argument parsing callbacks. """
|
|
11
|
+
|
|
12
|
+
# [(parse text, [(key, pre, post), ...], skip keys, expected (as dict)), ...]
|
|
13
|
+
test_cases = [
|
|
14
|
+
# Empty
|
|
15
|
+
(
|
|
16
|
+
"",
|
|
17
|
+
[],
|
|
18
|
+
[],
|
|
19
|
+
{
|
|
20
|
+
'_pre_extra_state_': {},
|
|
21
|
+
'_post_extra_state_': {},
|
|
22
|
+
},
|
|
23
|
+
),
|
|
24
|
+
|
|
25
|
+
# Single Callbacks
|
|
26
|
+
(
|
|
27
|
+
"",
|
|
28
|
+
[
|
|
29
|
+
('test', functools.partial(_pre_callback_append, value = 1), functools.partial(_post_callback_append, value = 2)),
|
|
30
|
+
],
|
|
31
|
+
[],
|
|
32
|
+
{
|
|
33
|
+
'_pre_extra_state_': {
|
|
34
|
+
'append': [1],
|
|
35
|
+
},
|
|
36
|
+
'_post_extra_state_': {
|
|
37
|
+
'append': [2],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
),
|
|
41
|
+
|
|
42
|
+
# Double Callbacks
|
|
43
|
+
(
|
|
44
|
+
"",
|
|
45
|
+
[
|
|
46
|
+
('test1', functools.partial(_pre_callback_append, value = 1), functools.partial(_post_callback_append, value = 2)),
|
|
47
|
+
('test2', functools.partial(_pre_callback_append, value = 3), functools.partial(_post_callback_append, value = 4)),
|
|
48
|
+
],
|
|
49
|
+
[],
|
|
50
|
+
{
|
|
51
|
+
'_pre_extra_state_': {
|
|
52
|
+
'append': [1, 3],
|
|
53
|
+
},
|
|
54
|
+
'_post_extra_state_': {
|
|
55
|
+
'append': [2, 4],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
),
|
|
59
|
+
|
|
60
|
+
# Split Callbacks
|
|
61
|
+
(
|
|
62
|
+
"",
|
|
63
|
+
[
|
|
64
|
+
('test1', functools.partial(_pre_callback_append, value = 1), None),
|
|
65
|
+
('test2', None, functools.partial(_post_callback_append, value = 4)),
|
|
66
|
+
],
|
|
67
|
+
[],
|
|
68
|
+
{
|
|
69
|
+
'_pre_extra_state_': {
|
|
70
|
+
'append': [1],
|
|
71
|
+
},
|
|
72
|
+
'_post_extra_state_': {
|
|
73
|
+
'append': [4],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
),
|
|
77
|
+
|
|
78
|
+
# Override Callbacks
|
|
79
|
+
(
|
|
80
|
+
"",
|
|
81
|
+
[
|
|
82
|
+
('test', functools.partial(_pre_callback_append, value = 1), functools.partial(_post_callback_append, value = 2)),
|
|
83
|
+
('test', functools.partial(_pre_callback_append, value = 3), functools.partial(_post_callback_append, value = 4)),
|
|
84
|
+
],
|
|
85
|
+
[],
|
|
86
|
+
{
|
|
87
|
+
'_pre_extra_state_': {
|
|
88
|
+
'append': [3],
|
|
89
|
+
},
|
|
90
|
+
'_post_extra_state_': {
|
|
91
|
+
'append': [4],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
for (i, test_case) in enumerate(test_cases):
|
|
98
|
+
(text, registrations, skip_keys, expected) = test_case
|
|
99
|
+
|
|
100
|
+
with self.subTest(msg = f"Case {i} ('{text}'):"):
|
|
101
|
+
parser = edq.core.argparser.Parser(f"Case {i}")
|
|
102
|
+
for (key, pre, post) in registrations:
|
|
103
|
+
parser.register_callbacks(key, pre, post)
|
|
104
|
+
|
|
105
|
+
args = parser.parse_args(text.split(), skip_keys = skip_keys)
|
|
106
|
+
|
|
107
|
+
actual = vars(args)
|
|
108
|
+
self.assertJSONDictEqual(expected, actual)
|
|
109
|
+
|
|
110
|
+
def _pre_callback_append(parser, extra_state, key = 'append', value = None) -> None:
|
|
111
|
+
""" Append the given value into the extra state. """
|
|
112
|
+
|
|
113
|
+
if (key not in extra_state):
|
|
114
|
+
extra_state[key] = []
|
|
115
|
+
|
|
116
|
+
extra_state[key].append(value)
|
|
117
|
+
|
|
118
|
+
def _post_callback_append(parser, args, extra_state, key = 'append', value = None) -> None:
|
|
119
|
+
""" Append the given value into the extra state. """
|
|
120
|
+
|
|
121
|
+
if (key not in extra_state):
|
|
122
|
+
extra_state[key] = []
|
|
123
|
+
|
|
124
|
+
extra_state[key].append(value)
|