edq-utils 0.0.4__py3-none-any.whl → 0.0.6__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 +1 -1
- 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 +12 -5
- edq/cli/version.py +2 -1
- edq/core/argparser.py +28 -3
- edq/core/config.py +268 -0
- edq/core/config_test.py +1038 -0
- edq/procedure/verify_exchanges.py +85 -0
- edq/testing/asserts.py +0 -1
- edq/testing/cli.py +107 -29
- edq/testing/cli_test.py +8 -1
- edq/testing/httpserver.py +553 -0
- edq/testing/httpserver_test.py +424 -0
- edq/testing/run.py +40 -10
- 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/platform_skip.txt +5 -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/tiny.png +0 -0
- edq/testing/unittest.py +26 -6
- edq/util/dirent.py +2 -0
- edq/util/dirent_test.py +43 -32
- edq/util/json.py +21 -4
- edq/util/net.py +894 -0
- edq_utils-0.0.6.dist-info/METADATA +156 -0
- edq_utils-0.0.6.dist-info/RECORD +78 -0
- edq/util/testdata/dirent-operations/dir_1/b.txt +0 -1
- edq/util/testdata/dirent-operations/dir_1/dir_2/c.txt +0 -1
- edq/util/testdata/dirent-operations/symlink_a.txt +0 -1
- edq/util/testdata/dirent-operations/symlink_dir_1/b.txt +0 -1
- edq/util/testdata/dirent-operations/symlink_dir_1/dir_2/c.txt +0 -1
- edq/util/testdata/dirent-operations/symlink_file_empty +0 -0
- edq_utils-0.0.4.dist-info/METADATA +0 -63
- edq_utils-0.0.4.dist-info/RECORD +0 -41
- /edq/{util/testdata/dirent-operations/file_empty → procedure/__init__.py} +0 -0
- /edq/{util/testdata/dirent-operations → testing/testdata/http/files}/a.txt +0 -0
- {edq_utils-0.0.4.dist-info → edq_utils-0.0.6.dist-info}/WHEEL +0 -0
- {edq_utils-0.0.4.dist-info → edq_utils-0.0.6.dist-info}/licenses/LICENSE +0 -0
- {edq_utils-0.0.4.dist-info → edq_utils-0.0.6.dist-info}/top_level.txt +0 -0
|
Binary file
|
edq/testing/unittest.py
CHANGED
|
@@ -14,25 +14,45 @@ class BaseTest(unittest.TestCase):
|
|
|
14
14
|
maxDiff = None
|
|
15
15
|
""" Don't limit the size of diffs. """
|
|
16
16
|
|
|
17
|
-
def assertJSONDictEqual(self, a: typing.
|
|
17
|
+
def assertJSONDictEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None: # pylint: disable=invalid-name
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
Like unittest.TestCase.assertDictEqual(),
|
|
20
|
+
but will try to convert each comparison argument to a dict if it is not already,
|
|
21
|
+
and uses a default assertion message containing the full JSON representation of the arguments.
|
|
20
22
|
"""
|
|
21
23
|
|
|
24
|
+
if (not isinstance(a, dict)):
|
|
25
|
+
if (isinstance(a, edq.util.json.DictConverter)):
|
|
26
|
+
a = a.to_dict()
|
|
27
|
+
else:
|
|
28
|
+
a = vars(a)
|
|
29
|
+
|
|
30
|
+
if (not isinstance(b, dict)):
|
|
31
|
+
if (isinstance(b, edq.util.json.DictConverter)):
|
|
32
|
+
b = b.to_dict()
|
|
33
|
+
else:
|
|
34
|
+
b = vars(b)
|
|
35
|
+
|
|
22
36
|
a_json = edq.util.json.dumps(a, indent = 4)
|
|
23
37
|
b_json = edq.util.json.dumps(b, indent = 4)
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
if (message is None):
|
|
40
|
+
message = FORMAT_STR % (a_json, b_json)
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
super().assertDictEqual(a, b, msg = message)
|
|
43
|
+
|
|
44
|
+
def assertJSONListEqual(self, a: typing.List[typing.Any], b: typing.List[typing.Any], message: typing.Union[str, None] = None) -> None: # pylint: disable=invalid-name
|
|
28
45
|
"""
|
|
29
|
-
Call
|
|
46
|
+
Call assertDictEqual(), but supply a default message containing the full JSON representation of the arguments.
|
|
30
47
|
"""
|
|
31
48
|
|
|
32
49
|
a_json = edq.util.json.dumps(a, indent = 4)
|
|
33
50
|
b_json = edq.util.json.dumps(b, indent = 4)
|
|
34
51
|
|
|
35
|
-
|
|
52
|
+
if (message is None):
|
|
53
|
+
message = FORMAT_STR % (a_json, b_json)
|
|
54
|
+
|
|
55
|
+
super().assertListEqual(a, b, msg = message)
|
|
36
56
|
|
|
37
57
|
def format_error_string(self, ex: typing.Union[BaseException, None]) -> str:
|
|
38
58
|
"""
|
edq/util/dirent.py
CHANGED
|
@@ -41,6 +41,8 @@ def get_temp_path(prefix: str = '', suffix: str = '', rm: bool = True) -> str:
|
|
|
41
41
|
while ((path is None) or exists(path)):
|
|
42
42
|
path = os.path.join(tempfile.gettempdir(), prefix + str(uuid.uuid4()) + suffix)
|
|
43
43
|
|
|
44
|
+
path = os.path.realpath(path)
|
|
45
|
+
|
|
44
46
|
if (rm):
|
|
45
47
|
atexit.register(remove, path)
|
|
46
48
|
|
edq/util/dirent_test.py
CHANGED
|
@@ -3,32 +3,51 @@ import os
|
|
|
3
3
|
import edq.testing.unittest
|
|
4
4
|
import edq.util.dirent
|
|
5
5
|
|
|
6
|
-
THIS_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
|
|
7
|
-
TEST_BASE_DIR = os.path.join(THIS_DIR, 'testdata', 'dirent-operations')
|
|
8
|
-
"""
|
|
9
|
-
This test data directory is laid out as:
|
|
10
|
-
.
|
|
11
|
-
├── a.txt
|
|
12
|
-
├── dir_1
|
|
13
|
-
│ ├── b.txt
|
|
14
|
-
│ └── dir_2
|
|
15
|
-
│ └── c.txt
|
|
16
|
-
├── dir_empty
|
|
17
|
-
├── file_empty
|
|
18
|
-
├── symlink_a.txt -> a.txt
|
|
19
|
-
├── symlink_dir_1 -> dir_1
|
|
20
|
-
├── symlink_dir_empty -> dir_empty
|
|
21
|
-
└── symlink_file_empty -> file_empty
|
|
22
|
-
|
|
23
|
-
Where non-empty files are filled with their filename (without the extension).
|
|
24
|
-
dir_empty will not exist in the repository (since it is an empty directory),
|
|
25
|
-
but it will be created by _prep_temp_dir().
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
6
|
DIRENT_TYPE_DIR = 'dir'
|
|
29
7
|
DIRENT_TYPE_FILE = 'file'
|
|
30
8
|
DIRENT_TYPE_BROKEN_SYMLINK = 'broken_symlink'
|
|
31
9
|
|
|
10
|
+
def create_test_dir(temp_dir_prefix: str) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Create a temp dir and populate it with dirents for testing.
|
|
13
|
+
|
|
14
|
+
This test data directory is laid out as:
|
|
15
|
+
.
|
|
16
|
+
├── a.txt
|
|
17
|
+
├── dir_1
|
|
18
|
+
│ ├── b.txt
|
|
19
|
+
│ └── dir_2
|
|
20
|
+
│ └── c.txt
|
|
21
|
+
├── dir_empty
|
|
22
|
+
├── file_empty
|
|
23
|
+
├── symlink_a.txt -> a.txt
|
|
24
|
+
├── symlink_dir_1 -> dir_1
|
|
25
|
+
├── symlink_dir_empty -> dir_empty
|
|
26
|
+
└── symlink_file_empty -> file_empty
|
|
27
|
+
|
|
28
|
+
Where non-empty files are filled with their filename (without the extension).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
temp_dir = edq.util.dirent.get_temp_dir(prefix = temp_dir_prefix)
|
|
32
|
+
|
|
33
|
+
# Dirs
|
|
34
|
+
edq.util.dirent.mkdir(os.path.join(temp_dir, 'dir_1', 'dir_2'))
|
|
35
|
+
edq.util.dirent.mkdir(os.path.join(temp_dir, 'dir_empty'))
|
|
36
|
+
|
|
37
|
+
# Files
|
|
38
|
+
edq.util.dirent.write_file(os.path.join(temp_dir, 'a.txt'), 'a')
|
|
39
|
+
edq.util.dirent.write_file(os.path.join(temp_dir, 'file_empty'), '')
|
|
40
|
+
edq.util.dirent.write_file(os.path.join(temp_dir, 'dir_1', 'b.txt'), 'b')
|
|
41
|
+
edq.util.dirent.write_file(os.path.join(temp_dir, 'dir_1', 'dir_2', 'c.txt'), 'c')
|
|
42
|
+
|
|
43
|
+
# Links
|
|
44
|
+
os.symlink('a.txt', os.path.join(temp_dir, 'symlink_a.txt'))
|
|
45
|
+
os.symlink('dir_1', os.path.join(temp_dir, 'symlink_dir_1'))
|
|
46
|
+
os.symlink('dir_empty', os.path.join(temp_dir, 'symlink_dir_empty'))
|
|
47
|
+
os.symlink('file_empty', os.path.join(temp_dir, 'symlink_file_empty'))
|
|
48
|
+
|
|
49
|
+
return temp_dir
|
|
50
|
+
|
|
32
51
|
class TestDirent(edq.testing.unittest.BaseTest):
|
|
33
52
|
""" Test basic operations on dirents. """
|
|
34
53
|
|
|
@@ -363,10 +382,7 @@ class TestDirent(edq.testing.unittest.BaseTest):
|
|
|
363
382
|
self.assertEqual(expected_contents, actual_contents)
|
|
364
383
|
|
|
365
384
|
def test_copy_contents_base(self):
|
|
366
|
-
"""
|
|
367
|
-
Test copying the contents of a dirent.
|
|
368
|
-
Note that the base functionality of copy_contents() is tested by test_setup().
|
|
369
|
-
"""
|
|
385
|
+
""" Test copying the contents of a dirent. """
|
|
370
386
|
|
|
371
387
|
# [(source, dest, no clobber?, error substring), ...]
|
|
372
388
|
test_cases = [
|
|
@@ -877,12 +893,7 @@ class TestDirent(edq.testing.unittest.BaseTest):
|
|
|
877
893
|
self._check_existing_paths(temp_dir, expected_paths)
|
|
878
894
|
|
|
879
895
|
def _prep_temp_dir(self):
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
edq.util.dirent.mkdir(os.path.join(temp_dir, 'dir_empty'))
|
|
883
|
-
edq.util.dirent.copy_contents(TEST_BASE_DIR, temp_dir)
|
|
884
|
-
|
|
885
|
-
return temp_dir
|
|
896
|
+
return create_test_dir('edq_test_dirent_')
|
|
886
897
|
|
|
887
898
|
def _check_existing_paths(self, base_dir, raw_paths):
|
|
888
899
|
"""
|
edq/util/json.py
CHANGED
|
@@ -4,37 +4,45 @@ Specifically, we try to be flexible when reading (using JSON5),
|
|
|
4
4
|
and strict when writing (using vanilla JSON).
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import abc
|
|
8
7
|
import enum
|
|
9
8
|
import json
|
|
9
|
+
import os
|
|
10
10
|
import typing
|
|
11
11
|
|
|
12
12
|
import json5
|
|
13
13
|
|
|
14
14
|
import edq.util.dirent
|
|
15
15
|
|
|
16
|
-
class DictConverter(
|
|
16
|
+
class DictConverter():
|
|
17
17
|
"""
|
|
18
18
|
A base class for class that can represent (serialize) and reconstruct (deserialize) themselves as/from a dict.
|
|
19
19
|
The intention is that the dict can then be cleanly converted to/from JSON.
|
|
20
|
+
|
|
21
|
+
General (but inefficient) implementations of several core Python equality, comparison, and representation methods are provided.
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
|
-
@abc.abstractmethod
|
|
23
24
|
def to_dict(self) -> typing.Dict[str, typing.Any]:
|
|
24
25
|
"""
|
|
25
26
|
Return a dict that can be used to represent this object.
|
|
26
27
|
If the dict is passed to from_dict(), an identical object should be reconstructed.
|
|
28
|
+
|
|
29
|
+
A general (but inefficient) implementation is provided by default.
|
|
27
30
|
"""
|
|
28
31
|
|
|
32
|
+
return vars(self).copy()
|
|
33
|
+
|
|
29
34
|
@classmethod
|
|
30
|
-
@abc.abstractmethod
|
|
31
35
|
# Note that `typing.Self` is returned, but that is introduced in Python 3.12.
|
|
32
36
|
def from_dict(cls, data: typing.Dict[str, typing.Any]) -> typing.Any:
|
|
33
37
|
"""
|
|
34
38
|
Return an instance of this subclass created using the given dict.
|
|
35
39
|
If the dict came from to_dict(), the returned object should be identical to the original.
|
|
40
|
+
|
|
41
|
+
A general (but inefficient) implementation is provided by default.
|
|
36
42
|
"""
|
|
37
43
|
|
|
44
|
+
return cls(**data)
|
|
45
|
+
|
|
38
46
|
def __eq__(self, other: object) -> bool:
|
|
39
47
|
"""
|
|
40
48
|
Check for equality.
|
|
@@ -49,6 +57,12 @@ class DictConverter(abc.ABC):
|
|
|
49
57
|
|
|
50
58
|
return bool(self.to_dict() == other.to_dict()) # type: ignore[attr-defined]
|
|
51
59
|
|
|
60
|
+
def __lt__(self, other: 'DictConverter') -> bool:
|
|
61
|
+
return dumps(self) < dumps(other)
|
|
62
|
+
|
|
63
|
+
def __hash__(self) -> int:
|
|
64
|
+
return hash(dumps(self))
|
|
65
|
+
|
|
52
66
|
def __str__(self) -> str:
|
|
53
67
|
return dumps(self)
|
|
54
68
|
|
|
@@ -107,6 +121,9 @@ def load_path(
|
|
|
107
121
|
otherwise use JSON5.
|
|
108
122
|
"""
|
|
109
123
|
|
|
124
|
+
if (os.path.isdir(path)):
|
|
125
|
+
raise IsADirectoryError(f"Cannot open JSON file, expected a file but got a directory at '{path}'.")
|
|
126
|
+
|
|
110
127
|
try:
|
|
111
128
|
with open(path, 'r', encoding = encoding) as file:
|
|
112
129
|
return load(file, strict = strict, **kwargs)
|