edq-utils 0.2.3__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.
Potentially problematic release.
This version of edq-utils might be problematic. Click here for more details.
- edq/__init__.py +5 -0
- edq/cli/__init__.py +0 -0
- edq/cli/__main__.py +17 -0
- edq/cli/config/__init__.py +3 -0
- edq/cli/config/__main__.py +15 -0
- edq/cli/config/list.py +69 -0
- edq/cli/http/__init__.py +3 -0
- edq/cli/http/__main__.py +15 -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/__main__.py +15 -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/cli.py +151 -0
- edq/util/dirent.py +346 -0
- edq/util/dirent_test.py +1004 -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 +1047 -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.2.3.dist-info/METADATA +164 -0
- edq_utils-0.2.3.dist-info/RECORD +88 -0
- edq_utils-0.2.3.dist-info/WHEEL +5 -0
- edq_utils-0.2.3.dist-info/licenses/LICENSE +21 -0
- edq_utils-0.2.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a
|
|
Binary file
|
edq/testing/unittest.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
import edq.util.json
|
|
5
|
+
import edq.util.reflection
|
|
6
|
+
|
|
7
|
+
FORMAT_STR: str = "\n--- Expected ---\n%s\n--- Actual ---\n%s\n---\n"
|
|
8
|
+
|
|
9
|
+
class BaseTest(unittest.TestCase):
|
|
10
|
+
"""
|
|
11
|
+
A base class for unit tests.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
maxDiff = None
|
|
15
|
+
""" Don't limit the size of diffs. """
|
|
16
|
+
|
|
17
|
+
def assertJSONEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None: # pylint: disable=invalid-name
|
|
18
|
+
"""
|
|
19
|
+
Like unittest.TestCase.assertEqual(),
|
|
20
|
+
but uses a default assertion message containing the full JSON representation of the arguments.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
a_json = edq.util.json.dumps(a, indent = 4)
|
|
24
|
+
b_json = edq.util.json.dumps(b, indent = 4)
|
|
25
|
+
|
|
26
|
+
if (message is None):
|
|
27
|
+
message = FORMAT_STR % (a_json, b_json)
|
|
28
|
+
|
|
29
|
+
super().assertEqual(a, b, msg = message)
|
|
30
|
+
|
|
31
|
+
def assertJSONDictEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None: # pylint: disable=invalid-name
|
|
32
|
+
"""
|
|
33
|
+
Like unittest.TestCase.assertDictEqual(),
|
|
34
|
+
but will try to convert each comparison argument to a dict if it is not already,
|
|
35
|
+
and uses a default assertion message containing the full JSON representation of the arguments.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
if (not isinstance(a, dict)):
|
|
39
|
+
if (isinstance(a, edq.util.json.DictConverter)):
|
|
40
|
+
a = a.to_dict()
|
|
41
|
+
else:
|
|
42
|
+
a = vars(a)
|
|
43
|
+
|
|
44
|
+
if (not isinstance(b, dict)):
|
|
45
|
+
if (isinstance(b, edq.util.json.DictConverter)):
|
|
46
|
+
b = b.to_dict()
|
|
47
|
+
else:
|
|
48
|
+
b = vars(b)
|
|
49
|
+
|
|
50
|
+
a_json = edq.util.json.dumps(a, indent = 4)
|
|
51
|
+
b_json = edq.util.json.dumps(b, indent = 4)
|
|
52
|
+
|
|
53
|
+
if (message is None):
|
|
54
|
+
message = FORMAT_STR % (a_json, b_json)
|
|
55
|
+
|
|
56
|
+
super().assertDictEqual(a, b, msg = message)
|
|
57
|
+
|
|
58
|
+
def assertJSONListEqual(self, a: typing.List[typing.Any], b: typing.List[typing.Any], message: typing.Union[str, None] = None) -> None: # pylint: disable=invalid-name
|
|
59
|
+
"""
|
|
60
|
+
Call assertDictEqual(), but supply a default message containing the full JSON representation of the arguments.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
a_json = edq.util.json.dumps(a, indent = 4)
|
|
64
|
+
b_json = edq.util.json.dumps(b, indent = 4)
|
|
65
|
+
|
|
66
|
+
if (message is None):
|
|
67
|
+
message = FORMAT_STR % (a_json, b_json)
|
|
68
|
+
|
|
69
|
+
super().assertListEqual(a, b, msg = message)
|
|
70
|
+
|
|
71
|
+
def format_error_string(self, ex: typing.Union[BaseException, None]) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Format an error string from an exception so it can be checked for testing.
|
|
74
|
+
The type of the error will be included,
|
|
75
|
+
and any nested errors will be joined together.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
parts = []
|
|
79
|
+
|
|
80
|
+
while (ex is not None):
|
|
81
|
+
type_name = edq.util.reflection.get_qualified_name(ex)
|
|
82
|
+
message = str(ex)
|
|
83
|
+
|
|
84
|
+
parts.append(f"{type_name}: {message}")
|
|
85
|
+
|
|
86
|
+
ex = ex.__cause__
|
|
87
|
+
|
|
88
|
+
return "; ".join(parts)
|
edq/util/__init__.py
ADDED
edq/util/cli.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Show the CLI tools available in this package.
|
|
3
|
+
|
|
4
|
+
This will look for objects that "look like" CLI tools.
|
|
5
|
+
A package looks like a CLI package if it has a __main__.py file.
|
|
6
|
+
A module looks like a CLI tool if it has the following functions:
|
|
7
|
+
- `def _get_parser() -> argparse.ArgumentParser:`
|
|
8
|
+
- `def run_cli(args: argparse.Namespace) -> int:`
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import inspect
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
import edq.util.dirent
|
|
16
|
+
import edq.util.pyimport
|
|
17
|
+
|
|
18
|
+
def auto_list(
|
|
19
|
+
recursive: bool = False,
|
|
20
|
+
skip_dirs: bool = False,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Print the caller's docstring and call _list_dir() on it,
|
|
24
|
+
but will figure out the package's docstring, base_dir, and command_prefix automatically.
|
|
25
|
+
This will use the inspect library, so only use in places that use code normally.
|
|
26
|
+
The first stack frame not in this file will be used.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
this_path = os.path.realpath(__file__)
|
|
30
|
+
|
|
31
|
+
caller_frame_info = None
|
|
32
|
+
for frame_info in inspect.stack():
|
|
33
|
+
if (edq.util.dirent.same(this_path, frame_info.filename)):
|
|
34
|
+
# Ignore this file.
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
caller_frame_info = frame_info
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
if (caller_frame_info is None):
|
|
41
|
+
raise ValueError("Unable to determine caller's stack frame.")
|
|
42
|
+
|
|
43
|
+
path = caller_frame_info.filename
|
|
44
|
+
base_dir = os.path.dirname(path)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
module = inspect.getmodule(caller_frame_info.frame)
|
|
48
|
+
if (module is None):
|
|
49
|
+
raise ValueError(f"Unable to get module for '{path}'.")
|
|
50
|
+
except Exception as ex:
|
|
51
|
+
raise ValueError("Unable to get caller information for listing CLI information.") from ex
|
|
52
|
+
|
|
53
|
+
if (module.__package__ is None):
|
|
54
|
+
raise ValueError(f"Caller module has no package information: '{path}'.")
|
|
55
|
+
|
|
56
|
+
if (module.__doc__ is None):
|
|
57
|
+
raise ValueError(f"Caller module has no docstring: '{path}'.")
|
|
58
|
+
|
|
59
|
+
print(module.__doc__.strip())
|
|
60
|
+
_list_dir(base_dir, module.__package__, recursive, skip_dirs)
|
|
61
|
+
|
|
62
|
+
def _list_dir(base_dir: str, command_prefix: str, recursive: bool, skip_dirs: bool) -> None:
|
|
63
|
+
""" List/descend the given dir. """
|
|
64
|
+
|
|
65
|
+
for dirent in sorted(os.listdir(base_dir)):
|
|
66
|
+
path = os.path.join(base_dir, dirent)
|
|
67
|
+
cmd = command_prefix + '.' + os.path.splitext(dirent)[0]
|
|
68
|
+
|
|
69
|
+
if (dirent.startswith('__')):
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
if (os.path.isfile(path)):
|
|
73
|
+
_handle_file(path, cmd)
|
|
74
|
+
else:
|
|
75
|
+
if (not skip_dirs):
|
|
76
|
+
_handle_dir(path, cmd)
|
|
77
|
+
|
|
78
|
+
if (recursive):
|
|
79
|
+
_list_dir(path, cmd, recursive, skip_dirs)
|
|
80
|
+
|
|
81
|
+
def _handle_file(path: str, cmd: str) -> None:
|
|
82
|
+
""" Process a file (possible module). """
|
|
83
|
+
|
|
84
|
+
if (not path.endswith('.py')):
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
module = edq.util.pyimport.import_path(path)
|
|
89
|
+
except Exception:
|
|
90
|
+
print("ERROR Importing: ", path)
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if ('_get_parser' not in dir(module)):
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
parser = module._get_parser()
|
|
97
|
+
parser.prog = 'python3 -m ' + cmd
|
|
98
|
+
|
|
99
|
+
print()
|
|
100
|
+
print(cmd)
|
|
101
|
+
print(parser.description)
|
|
102
|
+
parser.print_usage()
|
|
103
|
+
|
|
104
|
+
def _handle_dir(path: str, cmd: str) -> None:
|
|
105
|
+
""" Process a dir (possible package). """
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
module = edq.util.pyimport.import_path(os.path.join(path, '__main__.py'))
|
|
109
|
+
except Exception:
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
description = module.__doc__.strip()
|
|
113
|
+
|
|
114
|
+
print()
|
|
115
|
+
print(cmd + '.*')
|
|
116
|
+
print(description)
|
|
117
|
+
print(f"See `python3 -m {cmd}` for more information.")
|
|
118
|
+
|
|
119
|
+
def _get_parser() -> argparse.ArgumentParser:
|
|
120
|
+
parser = argparse.ArgumentParser(
|
|
121
|
+
description = __doc__.strip(),
|
|
122
|
+
epilog = ("Note that you don't need to provide a package as an argument,"
|
|
123
|
+
+ " since you already called this on the target package."))
|
|
124
|
+
|
|
125
|
+
parser.add_argument('-r', '--recursive', dest = 'recursive',
|
|
126
|
+
action = 'store_true', default = False,
|
|
127
|
+
help = 'Recur into each package to look for tools and subpackages (default: %(default)s).')
|
|
128
|
+
|
|
129
|
+
parser.add_argument('-s', '--skip-dirs', dest = 'skip_dirs',
|
|
130
|
+
action = 'store_true', default = False,
|
|
131
|
+
help = ('Do not output information about directories/packages,'
|
|
132
|
+
+ ' only tools/files/modules (default: %(default)s).'))
|
|
133
|
+
|
|
134
|
+
return parser
|
|
135
|
+
|
|
136
|
+
def run_cli(args: argparse.Namespace) -> int:
|
|
137
|
+
"""
|
|
138
|
+
List the caller's dir.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
auto_list(recursive = args.recursive, skip_dirs = args.skip_dirs)
|
|
142
|
+
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
def main() -> int:
|
|
146
|
+
"""
|
|
147
|
+
Run as if this process has been called as a executable.
|
|
148
|
+
This will parse the command line and list the caller's dir.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
return run_cli(_get_parser().parse_args())
|