edq-utils 0.1.1__tar.gz → 0.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.

Potentially problematic release.


This version of edq-utils might be problematic. Click here for more details.

Files changed (104) hide show
  1. {edq_utils-0.1.1 → edq_utils-0.1.3}/.pylintrc +0 -4
  2. {edq_utils-0.1.1 → edq_utils-0.1.3}/PKG-INFO +1 -1
  3. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/__init__.py +1 -1
  4. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/cli.py +3 -2
  5. edq_utils-0.1.3/edq/testing/testdata/cli/tests/help_base.txt +9 -0
  6. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/unittest.py +14 -0
  7. edq_utils-0.1.3/edq/util/hash.py +38 -0
  8. edq_utils-0.1.3/edq/util/hash_test.py +89 -0
  9. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/net.py +8 -1
  10. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq_utils.egg-info/PKG-INFO +1 -1
  11. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq_utils.egg-info/SOURCES.txt +3 -0
  12. {edq_utils-0.1.1 → edq_utils-0.1.3}/.github/workflows/main.yml +0 -0
  13. {edq_utils-0.1.1 → edq_utils-0.1.3}/.gitignore +0 -0
  14. {edq_utils-0.1.1 → edq_utils-0.1.3}/.mypy.ini +0 -0
  15. {edq_utils-0.1.1 → edq_utils-0.1.3}/LICENSE +0 -0
  16. {edq_utils-0.1.1 → edq_utils-0.1.3}/README.md +0 -0
  17. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/__init__.py +0 -0
  18. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/config/__init__.py +0 -0
  19. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/config/list.py +0 -0
  20. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/http/__init__.py +0 -0
  21. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/http/exchange-server.py +0 -0
  22. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/http/send-exchange.py +0 -0
  23. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/http/verify-exchanges.py +0 -0
  24. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/testing/__init__.py +0 -0
  25. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/testing/cli-test.py +0 -0
  26. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/cli/version.py +0 -0
  27. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/core/__init__.py +0 -0
  28. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/core/argparser.py +0 -0
  29. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/core/argparser_test.py +0 -0
  30. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/core/config.py +0 -0
  31. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/core/config_test.py +0 -0
  32. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/core/log.py +0 -0
  33. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/core/version.py +0 -0
  34. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/procedure/__init__.py +0 -0
  35. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/procedure/verify_exchanges.py +0 -0
  36. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/py.typed +0 -0
  37. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/__init__.py +0 -0
  38. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/asserts.py +0 -0
  39. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/cli_test.py +0 -0
  40. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/httpserver.py +0 -0
  41. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/httpserver_test.py +0 -0
  42. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/run.py +0 -0
  43. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/data/configs/empty/edq-config.json +0 -0
  44. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/data/configs/simple-1/edq-config.json +0 -0
  45. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/data/configs/simple-2/edq-config.json +0 -0
  46. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/data/configs/value-number/edq-config.json +0 -0
  47. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/tests/config/list/config_list_base.txt +0 -0
  48. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/tests/config/list/config_list_config_value_number.txt +0 -0
  49. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/tests/config/list/config_list_ignore_config.txt +0 -0
  50. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/tests/config/list/config_list_no_config.txt +0 -0
  51. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/tests/config/list/config_list_show_origin.txt +0 -0
  52. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/tests/config/list/config_list_skip_header.txt +0 -0
  53. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/tests/platform_skip.txt +0 -0
  54. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/cli/tests/version_base.txt +0 -0
  55. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple.httpex.json +0 -0
  56. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_anchor.httpex.json +0 -0
  57. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_file.httpex.json +0 -0
  58. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_file_binary.httpex.json +0 -0
  59. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_file_get_params.httpex.json +0 -0
  60. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_file_multiple.httpex.json +0 -0
  61. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_file_name.httpex.json +0 -0
  62. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_file_post_multiple.httpex.json +0 -0
  63. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_file_post_params.httpex.json +0 -0
  64. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_headers.httpex.json +0 -0
  65. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_jsonresponse_dict.httpex.json +0 -0
  66. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_jsonresponse_list.httpex.json +0 -0
  67. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_params.httpex.json +0 -0
  68. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_post.httpex.json +0 -0
  69. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_post_params.httpex.json +0 -0
  70. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_post_urlparams.httpex.json +0 -0
  71. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/simple_urlparams.httpex.json +0 -0
  72. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/specialcase_listparams_explicit.httpex.json +0 -0
  73. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/exchanges/specialcase_listparams_url.httpex.json +0 -0
  74. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/files/a.txt +0 -0
  75. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/testing/testdata/http/files/tiny.png +0 -0
  76. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/__init__.py +0 -0
  77. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/dirent.py +0 -0
  78. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/dirent_test.py +0 -0
  79. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/json.py +0 -0
  80. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/json_test.py +0 -0
  81. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/pyimport.py +0 -0
  82. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/pyimport_test.py +0 -0
  83. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/reflection.py +0 -0
  84. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/time.py +0 -0
  85. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq/util/time_test.py +0 -0
  86. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq_utils.egg-info/dependency_links.txt +0 -0
  87. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq_utils.egg-info/requires.txt +0 -0
  88. {edq_utils-0.1.1 → edq_utils-0.1.3}/edq_utils.egg-info/top_level.txt +0 -0
  89. {edq_utils-0.1.1 → edq_utils-0.1.3}/pyproject.toml +0 -0
  90. {edq_utils-0.1.1 → edq_utils-0.1.3}/requirements-dev.txt +0 -0
  91. {edq_utils-0.1.1 → edq_utils-0.1.3}/requirements.txt +0 -0
  92. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/build_site.sh +0 -0
  93. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/check_all.sh +0 -0
  94. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/check_lint.sh +0 -0
  95. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/check_python_version.sh +0 -0
  96. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/check_strict_types.sh +0 -0
  97. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/check_types.sh +0 -0
  98. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/gen_docs.sh +0 -0
  99. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/run_tests.sh +0 -0
  100. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/template/html/css/style.css +0 -0
  101. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/template/html/favicon.ico +0 -0
  102. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/template/html/images/favicon.png +0 -0
  103. {edq_utils-0.1.1 → edq_utils-0.1.3}/scripts/template/html/index.html +0 -0
  104. {edq_utils-0.1.1 → edq_utils-0.1.3}/setup.cfg +0 -0
@@ -104,10 +104,6 @@ recursive=no
104
104
  # source root.
105
105
  source-roots=
106
106
 
107
- # When enabled, pylint would attempt to guess common misconfiguration and emit
108
- # user-friendly hints instead of false-positive error messages.
109
- suggestion-mode=yes
110
-
111
107
  # Allow loading of arbitrary C extensions. Extensions are imported into the
112
108
  # active Python interpreter and may run arbitrary code.
113
109
  unsafe-load-any-extension=no
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edq-utils
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Common utilities used by EduLinq Python projects.
5
5
  Author-email: Eriq Augustine <eriq@edulinq.org>
6
6
  License: MIT License
@@ -2,4 +2,4 @@
2
2
  General Python tools used by several EduLinq projects.
3
3
  """
4
4
 
5
- __version__ = '0.1.1'
5
+ __version__ = '0.1.3'
@@ -35,6 +35,7 @@ import edq.util.json
35
35
  import edq.util.pyimport
36
36
 
37
37
  TEST_CASE_SEP: str = '---'
38
+ OUTPUT_SEP: str = '+++'
38
39
  DATA_DIR_ID: str = '__DATA_DIR__'
39
40
  ABS_DATA_DIR_ID: str = '__ABS_DATA_DIR__'
40
41
  TEMP_DIR_ID: str = '__TEMP_DIR__'
@@ -149,7 +150,7 @@ class CLITestInfo:
149
150
  """
150
151
  Split stdout and stderr into different strings for testing.
151
152
  By default, these two will be combined.
152
- If both are non-empty, then they will be joined like: f"{stdout}\n{TEST_CASE_SEP}\n{stderr}".
153
+ If both are non-empty, then they will be joined like: f"{stdout}\n{OUTPUT_SEP}\n{stderr}".
153
154
  Otherwise, only the non-empty one will be present with no separator.
154
155
  Any stdout assertions will be applied to the combined text.
155
156
  """
@@ -293,7 +294,7 @@ def _get_test_method(test_name: str, path: str, data_dir: str) -> typing.Callabl
293
294
 
294
295
  if (not test_info.split_stdout_stderr):
295
296
  if ((len(stdout_text) > 0) and (len(stderr_text) > 0)):
296
- stdout_text = f"{stdout_text}\n{TEST_CASE_SEP}\n{stderr_text}"
297
+ stdout_text = f"{stdout_text}\n{OUTPUT_SEP}\n{stderr_text}"
297
298
  elif (len(stderr_text) > 0):
298
299
  stdout_text = stderr_text
299
300
 
@@ -0,0 +1,9 @@
1
+ {
2
+ "cli": "edq.cli.version",
3
+ "arguments": [
4
+ '--help'
5
+ ],
6
+ "error": true
7
+ }
8
+ ---
9
+ builtins.SystemExit: 0
@@ -14,6 +14,20 @@ class BaseTest(unittest.TestCase):
14
14
  maxDiff = None
15
15
  """ Don't limit the size of diffs. """
16
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
+
17
31
  def assertJSONDictEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None: # pylint: disable=invalid-name
18
32
  """
19
33
  Like unittest.TestCase.assertDictEqual(),
@@ -0,0 +1,38 @@
1
+ import hashlib
2
+ import typing
3
+
4
+ import edq.util.dirent
5
+
6
+ DEFAULT_CLIP_HASH_LENGTH: int = 8
7
+
8
+ def sha256_hex(payload: typing.Any, encoding: str = edq.util.dirent.DEFAULT_ENCODING) -> str:
9
+ """ Compute and return the hex string of the SHA3-256 encoding of the payload. """
10
+
11
+ digest = hashlib.new('sha256')
12
+ digest.update(payload.encode(encoding))
13
+ return digest.hexdigest()
14
+
15
+ def clip_text(text: str, max_length: int, hash_length: int = DEFAULT_CLIP_HASH_LENGTH) -> str:
16
+ """
17
+ Return a clipped version of the input text that is no longer than the specified length.
18
+ If the base text is found to be too long,
19
+ then enough if the tail of the text will be removed to insert a note about the clipping
20
+ and the first |hash_length| characters of the hash from sha256_hex().
21
+
22
+ Note that the max length is actually a soft cap.
23
+ Longer strings can be generated if the original text is shorter than the notification
24
+ that will be inserted into the clipped text.
25
+ """
26
+
27
+ if (len(text) <= max_length):
28
+ return text
29
+
30
+ hash_hex = sha256_hex(text)
31
+ notification = f"[text clipped {hash_hex[0:hash_length]}]"
32
+
33
+ # Don't clip the text if the final string would be longer.
34
+ if (len(notification) >= len(text)):
35
+ return text
36
+
37
+ keep_length = max(0, max_length - len(notification))
38
+ return text[0:keep_length] + notification
@@ -0,0 +1,89 @@
1
+ import edq.testing.unittest
2
+ import edq.util.hash
3
+
4
+ class TestHash(edq.testing.unittest.BaseTest):
5
+ """ Test hash-based operations. """
6
+
7
+ def test_sha256_hex_base(self):
8
+ """ Test the base sha256 hash. """
9
+
10
+ # [(input, expected), ...]
11
+ test_cases = [
12
+ ('foo', '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'),
13
+ ('abcdefghijklmnopqrstuvwxyz1234567890', '77d721c817f9d216c1fb783bcad9cdc20aaa2427402683f1f75dd6dfbe657470'),
14
+ ('', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'),
15
+ ]
16
+
17
+ for (i, test_case) in enumerate(test_cases):
18
+ (text, expected) = test_case
19
+
20
+ with self.subTest(msg = f"Case {i} ('{text}'):"):
21
+ actual = edq.util.hash.sha256_hex(text)
22
+ self.assertEqual(expected, actual)
23
+
24
+ def test_clip_text_base(self):
25
+ """ Test the base functionality of clip_text(). """
26
+
27
+ # [(text, max length, kwargs, expected), ...]
28
+ test_cases = [
29
+ # No Clip
30
+ (
31
+ 'abcdefghijklmnopqrstuvwxyz1234567890',
32
+ 100,
33
+ {},
34
+ 'abcdefghijklmnopqrstuvwxyz1234567890',
35
+ ),
36
+
37
+ # Base Clip
38
+ (
39
+ 'abcdefghijklmnopqrstuvwxyz1234567890',
40
+ 30,
41
+ {},
42
+ 'abcdefg[text clipped 77d721c8]',
43
+ ),
44
+
45
+ # Full Clip
46
+ (
47
+ 'abcdefghijklmnopqrstuvwxyz1234567890',
48
+ 23,
49
+ {},
50
+ '[text clipped 77d721c8]',
51
+ ),
52
+
53
+ # Over Clip
54
+ (
55
+ 'abcdefghijklmnopqrstuvwxyz1234567890',
56
+ 10,
57
+ {},
58
+ '[text clipped 77d721c8]',
59
+ ),
60
+
61
+ # Different Hash Length
62
+ (
63
+ 'abcdefghijklmnopqrstuvwxyz1234567890',
64
+ 30,
65
+ {'hash_length': 10},
66
+ 'abcde[text clipped 77d721c817]',
67
+ ),
68
+
69
+ # Notification Longer Than Text
70
+ (
71
+ 'abc',
72
+ 1,
73
+ {},
74
+ 'abc',
75
+ ),
76
+ (
77
+ 'abcdefghijklmnopqrstuvwxyz1234567890',
78
+ 10,
79
+ {'hash_length': 64},
80
+ 'abcdefghijklmnopqrstuvwxyz1234567890',
81
+ ),
82
+ ]
83
+
84
+ for (i, test_case) in enumerate(test_cases):
85
+ (text, max_length, kwargs, expected) = test_case
86
+
87
+ with self.subTest(msg = f"Case {i} ('{text}'):"):
88
+ actual = edq.util.hash.clip_text(text, max_length, **kwargs)
89
+ self.assertEqual(expected, actual)
@@ -19,6 +19,7 @@ import requests
19
19
  import requests_toolbelt.multipart.decoder
20
20
 
21
21
  import edq.util.dirent
22
+ import edq.util.hash
22
23
  import edq.util.json
23
24
  import edq.util.pyimport
24
25
 
@@ -30,6 +31,9 @@ DEFAULT_REQUEST_TIMEOUT_SECS: float = 10.0
30
31
 
31
32
  DEFAULT_HTTP_EXCHANGE_EXTENSION: str= '.httpex.json'
32
33
 
34
+ QUERY_CLIP_LENGTH: int = 100
35
+ """ If the query portion of an HTTPExhange being saved is longer than this, then clip the name. """
36
+
33
37
  ANCHOR_HEADER_KEY: str = 'edq-anchor'
34
38
  """
35
39
  By default, requests made via make_request() will send a header with this key that includes the anchor component of the URL.
@@ -729,8 +733,11 @@ def make_request(method: str, url: str,
729
733
 
730
734
  query = urllib.parse.urlencode(exchange.parameters)
731
735
  if (query != ''):
736
+ # The query can get very long, so we may have to clip it.
737
+ query_text = edq.util.hash.clip_text(query, QUERY_CLIP_LENGTH)
738
+
732
739
  # Note that the '?' is URL encoded.
733
- path += f"%3F{query}"
740
+ path += f"%3F{query_text}"
734
741
 
735
742
  path += f"_{method}{http_exchange_extension}"
736
743
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edq-utils
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Common utilities used by EduLinq Python projects.
5
5
  Author-email: Eriq Augustine <eriq@edulinq.org>
6
6
  License: MIT License
@@ -40,6 +40,7 @@ edq/testing/testdata/cli/data/configs/empty/edq-config.json
40
40
  edq/testing/testdata/cli/data/configs/simple-1/edq-config.json
41
41
  edq/testing/testdata/cli/data/configs/simple-2/edq-config.json
42
42
  edq/testing/testdata/cli/data/configs/value-number/edq-config.json
43
+ edq/testing/testdata/cli/tests/help_base.txt
43
44
  edq/testing/testdata/cli/tests/platform_skip.txt
44
45
  edq/testing/testdata/cli/tests/version_base.txt
45
46
  edq/testing/testdata/cli/tests/config/list/config_list_base.txt
@@ -72,6 +73,8 @@ edq/testing/testdata/http/files/tiny.png
72
73
  edq/util/__init__.py
73
74
  edq/util/dirent.py
74
75
  edq/util/dirent_test.py
76
+ edq/util/hash.py
77
+ edq/util/hash_test.py
75
78
  edq/util/json.py
76
79
  edq/util/json_test.py
77
80
  edq/util/net.py
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
File without changes
File without changes