edq-utils 0.0.2__py3-none-any.whl → 0.0.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 CHANGED
@@ -1 +1,5 @@
1
- __version__ = '0.0.2'
1
+ """
2
+ General Python tools used by several EduLinq projects.
3
+ """
4
+
5
+ __version__ = '0.0.3'
edq/py.typed ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ """
2
+ Testing infrastructure.
3
+ """
edq/testing/run.py CHANGED
@@ -11,10 +11,6 @@ import sys
11
11
  import typing
12
12
  import unittest
13
13
 
14
- THIS_DIR: str = os.path.join(os.path.dirname(os.path.realpath(__file__)))
15
- BASE_PACKAGE_DIR: str = os.path.join(THIS_DIR, '..')
16
- PROJECT_ROOT_DIR: str = os.path.join(BASE_PACKAGE_DIR, '..')
17
-
18
14
  DEFAULT_TEST_FILENAME_PATTERN: str = '*_test.py'
19
15
 
20
16
  def _collect_tests(suite: typing.Union[unittest.TestCase, unittest.suite.TestSuite]) -> typing.List[unittest.TestCase]:
@@ -41,13 +37,22 @@ def run(args: argparse.Namespace) -> int:
41
37
  Will raise if tests fail to load (e.g. syntax errors) and a suggested exit code otherwise.
42
38
  """
43
39
 
44
- # Start in the project's root and add it in the path.
45
- os.chdir(PROJECT_ROOT_DIR)
46
- sys.path.append(PROJECT_ROOT_DIR)
40
+ if (args.work_dir is not None):
41
+ os.chdir(args.work_dir)
42
+
43
+ if (args.path_additions is not None):
44
+ for path in args.path_additions:
45
+ sys.path.append(path)
46
+
47
+ if (args.test_dirs is None):
48
+ args.test_dirs = ['.']
47
49
 
48
50
  runner = unittest.TextTestRunner(verbosity = 3)
49
- discovered_suite = unittest.TestLoader().discover(BASE_PACKAGE_DIR, pattern = args.filename_pattern)
50
- test_cases = _collect_tests(discovered_suite)
51
+ test_cases = []
52
+
53
+ for test_dir in args.test_dirs:
54
+ discovered_suite = unittest.TestLoader().discover(test_dir, pattern = args.filename_pattern)
55
+ test_cases += _collect_tests(discovered_suite)
51
56
 
52
57
  tests = unittest.suite.TestSuite()
53
58
 
@@ -64,27 +69,43 @@ def run(args: argparse.Namespace) -> int:
64
69
  faults = len(result.errors) + len(result.failures)
65
70
 
66
71
  if (not result.wasSuccessful()):
67
- # This value will be used as an exit status, so don't larger than a byte.
72
+ # This value will be used as an exit status, so it should not be larger than a byte.
68
73
  # (Some higher values are used specially, so just keep it at a round number.)
69
74
  return max(1, min(faults, 100))
70
75
 
71
76
  return 0
72
77
 
73
78
  def main() -> int:
79
+ """ Parse the CLI arguments and run tests. """
80
+
74
81
  args = _get_parser().parse_args()
75
82
  return run(args)
76
83
 
77
84
  def _get_parser() -> argparse.ArgumentParser:
85
+ """ Build a parser for CLI arguments. """
86
+
78
87
  parser = argparse.ArgumentParser(description = 'Run unit tests discovered in this project.')
79
88
 
80
- parser.add_argument('pattern',
81
- action = 'store', type = str, default = None, nargs = '?',
82
- help = 'If supplied, only tests with names matching this pattern will be run. This pattern is used directly in re.search().')
89
+ parser.add_argument('--work-dir', dest = 'work_dir',
90
+ action = 'store', type = str, default = os.getcwd(),
91
+ help = 'Set the working directory when running tests, defaults to the current working directory (%(default)s).')
92
+
93
+ parser.add_argument('--tests-dir', dest = 'test_dirs',
94
+ action = 'append',
95
+ help = 'Discover tests from these directories. Defaults to the current directory.')
96
+
97
+ parser.add_argument('--add-path', dest = 'path_additions',
98
+ action = 'append',
99
+ help = 'If supplied, add this path the sys.path before running tests.')
83
100
 
84
101
  parser.add_argument('--filename-pattern', dest = 'filename_pattern',
85
102
  action = 'store', type = str, default = DEFAULT_TEST_FILENAME_PATTERN,
86
103
  help = 'The pattern to use to find test files (default: %(default)s).')
87
104
 
105
+ parser.add_argument('pattern',
106
+ action = 'store', type = str, default = None, nargs = '?',
107
+ help = 'If supplied, only tests with names matching this pattern will be run. This pattern is used directly in re.search().')
108
+
88
109
  return parser
89
110
 
90
111
  if __name__ == '__main__':
edq/testing/unittest.py CHANGED
@@ -1,4 +1,3 @@
1
- import json
2
1
  import typing
3
2
  import unittest
4
3
 
@@ -15,13 +14,21 @@ class BaseTest(unittest.TestCase):
15
14
  maxDiff = None
16
15
  """ Don't limit the size of diffs. """
17
16
 
18
- def assertJSONDictEqual(self, a: dict, b: dict) -> None:
17
+ def assertJSONDictEqual(self, a: dict, b: dict) -> None: # pylint: disable=invalid-name
18
+ """
19
+ Call assertDictEqual(), but supply a message containing the full JSON representation of the arguments.
20
+ """
21
+
19
22
  a_json = edq.util.json.dumps(a, indent = 4)
20
23
  b_json = edq.util.json.dumps(b, indent = 4)
21
24
 
22
25
  super().assertDictEqual(a, b, FORMAT_STR % (a_json, b_json))
23
26
 
24
- def assertJSONListEqual(self, a: list, b: list) -> None:
27
+ def assertJSONListEqual(self, a: list, b: list) -> None: # pylint: disable=invalid-name
28
+ """
29
+ Call assertListEqual(), but supply a message containing the full JSON representation of the arguments.
30
+ """
31
+
25
32
  a_json = edq.util.json.dumps(a, indent = 4)
26
33
  b_json = edq.util.json.dumps(b, indent = 4)
27
34
 
edq/util/__init__.py CHANGED
@@ -0,0 +1,3 @@
1
+ """
2
+ Low-level utilities.
3
+ """
edq/util/dirent.py CHANGED
@@ -13,6 +13,7 @@ import atexit
13
13
  import os
14
14
  import shutil
15
15
  import tempfile
16
+ import typing
16
17
  import uuid
17
18
 
18
19
  DEFAULT_ENCODING: str = 'utf-8'
@@ -247,7 +248,7 @@ def read_file(raw_path: str, strip: bool = True, encoding: str = DEFAULT_ENCODIN
247
248
  return contents
248
249
 
249
250
  def write_file(
250
- raw_path: str, contents: str,
251
+ raw_path: str, contents: typing.Union[str, None],
251
252
  strip: bool = True, newline: bool = True,
252
253
  encoding: str = DEFAULT_ENCODING,
253
254
  no_clobber = False) -> None:
@@ -276,6 +277,39 @@ def write_file(
276
277
  with open(path, 'w', encoding = encoding) as file:
277
278
  file.write(contents)
278
279
 
280
+ def read_file_bytes(raw_path: str) -> bytes:
281
+ """ Read the contents of a file as bytes. """
282
+
283
+ path = os.path.abspath(raw_path)
284
+
285
+ if (not exists(path)):
286
+ raise ValueError(f"Source of read bytes does not exist: '{raw_path}'.")
287
+
288
+ with open(path, 'rb') as file:
289
+ return file.read()
290
+
291
+ def write_file_bytes(
292
+ raw_path: str, contents: typing.Union[bytes, None],
293
+ no_clobber = False) -> None:
294
+ """
295
+ Write the contents of a file as bytes.
296
+ If clobbering, any existing dirent will be removed before write.
297
+ """
298
+
299
+ path = os.path.abspath(raw_path)
300
+
301
+ if (exists(path)):
302
+ if (no_clobber):
303
+ raise ValueError(f"Destination of write bytes already exists: '{raw_path}'.")
304
+
305
+ remove(path)
306
+
307
+ if (contents is None):
308
+ contents = b''
309
+
310
+ with open(path, 'wb') as file:
311
+ file.write(contents)
312
+
279
313
  def contains_path(parent: str, child: str) -> bool:
280
314
  """
281
315
  Check if the parent path contains the child path.
edq/util/dirent_test.py CHANGED
@@ -92,6 +92,124 @@ class TestDirent(edq.testing.unittest.BaseTest):
92
92
  actual = edq.util.dirent.contains_path(parent, child)
93
93
  self.assertEqual(expected, actual)
94
94
 
95
+ def test_read_write_file_bytes_base(self):
96
+ """ Test reading and writing a file as bytes. """
97
+
98
+ # [(path, write kwargs, read kwargs, write contents, expected contents, error substring), ...]
99
+ # All conent should be strings that will be encoded.
100
+ test_cases = [
101
+ # Base
102
+ (
103
+ "test.txt",
104
+ {},
105
+ {},
106
+ "test",
107
+ "test",
108
+ None,
109
+ ),
110
+
111
+ # Empty Write
112
+ (
113
+ "test.txt",
114
+ {},
115
+ {},
116
+ "",
117
+ "",
118
+ None,
119
+ ),
120
+
121
+ # None Write
122
+ (
123
+ "test.txt",
124
+ {},
125
+ {},
126
+ None,
127
+ "",
128
+ None,
129
+ ),
130
+
131
+ # Clobber
132
+ (
133
+ "a.txt",
134
+ {},
135
+ {},
136
+ "test",
137
+ "test",
138
+ None,
139
+ ),
140
+ (
141
+ "dir_1",
142
+ {},
143
+ {},
144
+ "test",
145
+ "test",
146
+ None,
147
+ ),
148
+ (
149
+ "symlink_a.txt",
150
+ {},
151
+ {},
152
+ "test",
153
+ "test",
154
+ None,
155
+ ),
156
+
157
+ # No Clobber
158
+ (
159
+ "a.txt",
160
+ {'no_clobber': True},
161
+ {},
162
+ "test",
163
+ "test",
164
+ 'already exists',
165
+ ),
166
+ (
167
+ "dir_1",
168
+ {'no_clobber': True},
169
+ {},
170
+ "test",
171
+ "test",
172
+ 'already exists',
173
+ ),
174
+ (
175
+ "symlink_a.txt",
176
+ {'no_clobber': True},
177
+ {},
178
+ "test",
179
+ "test",
180
+ 'already exists',
181
+ ),
182
+ ]
183
+
184
+ for (i, test_case) in enumerate(test_cases):
185
+ (path, write_options, read_options, write_contents, expected_contents, error_substring) = test_case
186
+
187
+ with self.subTest(msg = f"Case {i} ('{path}'):"):
188
+ temp_dir = self._prep_temp_dir()
189
+ path = os.path.join(temp_dir, path)
190
+
191
+ if (write_contents is not None):
192
+ write_contents = bytes(write_contents, edq.util.dirent.DEFAULT_ENCODING)
193
+
194
+ expected_contents = bytes(expected_contents, edq.util.dirent.DEFAULT_ENCODING)
195
+
196
+ try:
197
+ edq.util.dirent.write_file_bytes(path, write_contents, **write_options)
198
+ actual_contents = edq.util.dirent.read_file_bytes(path, **read_options)
199
+ except Exception as ex:
200
+ error_string = self.format_error_string(ex)
201
+ if (error_substring is None):
202
+ self.fail(f"Unexpected error: '{error_string}'.")
203
+
204
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
205
+
206
+ continue
207
+
208
+ if (error_substring is not None):
209
+ self.fail(f"Did not get expected error: '{error_substring}'.")
210
+
211
+ self.assertEqual(expected_contents, actual_contents)
212
+
95
213
  def test_read_write_file_base(self):
96
214
  """ Test reading and writing a file. """
97
215
 
@@ -157,7 +275,7 @@ class TestDirent(edq.testing.unittest.BaseTest):
157
275
  None,
158
276
  ),
159
277
 
160
- # None
278
+ # None Write
161
279
  (
162
280
  "test.txt",
163
281
  {'newline': False},
@@ -225,7 +343,6 @@ class TestDirent(edq.testing.unittest.BaseTest):
225
343
 
226
344
  with self.subTest(msg = f"Case {i} ('{path}'):"):
227
345
  temp_dir = self._prep_temp_dir()
228
-
229
346
  path = os.path.join(temp_dir, path)
230
347
 
231
348
  try:
@@ -243,7 +360,7 @@ class TestDirent(edq.testing.unittest.BaseTest):
243
360
  if (error_substring is not None):
244
361
  self.fail(f"Did not get expected error: '{error_substring}'.")
245
362
 
246
- self.assertEqual(expected_contents, actual_contents)
363
+ self.assertEqual(expected_contents, actual_contents)
247
364
 
248
365
  def test_copy_contents_base(self):
249
366
  """
edq/util/json.py CHANGED
@@ -4,6 +4,8 @@ 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
+ import enum
7
9
  import json
8
10
  import typing
9
11
 
@@ -11,6 +13,65 @@ import json5
11
13
 
12
14
  import edq.util.dirent
13
15
 
16
+ class DictConverter(abc.ABC):
17
+ """
18
+ A base class for class that can represent (serialize) and reconstruct (deserialize) themselves as/from a dict.
19
+ The intention is that the dict can then be cleanly converted to/from JSON.
20
+ """
21
+
22
+ @abc.abstractmethod
23
+ def to_dict(self) -> typing.Dict[str, typing.Any]:
24
+ """
25
+ Return a dict that can be used to represent this object.
26
+ If the dict is passed to from_dict(), an identical object should be reconstructed.
27
+ """
28
+
29
+ @classmethod
30
+ @abc.abstractmethod
31
+ # Note that `typing.Self` is returned, but that is introduced in Python 3.12.
32
+ def from_dict(cls, data: typing.Dict[str, typing.Any]) -> typing.Any:
33
+ """
34
+ Return an instance of this subclass created using the given dict.
35
+ If the dict came from to_dict(), the returned object should be identical to the original.
36
+ """
37
+
38
+ def __eq__(self, other: object) -> bool:
39
+ """
40
+ Check for equality.
41
+
42
+ This check uses to_dict() and compares the results.
43
+ This may not be complete or efficient depending on the child class.
44
+ """
45
+
46
+ # Note the hard type check (done so we can keep this method general).
47
+ if (type(self) != type(other)): # pylint: disable=unidiomatic-typecheck
48
+ return False
49
+
50
+ return self.to_dict() == other.to_dict() # type: ignore[attr-defined]
51
+
52
+ def __str__(self) -> str:
53
+ return dumps(self)
54
+
55
+ def __repr__(self) -> str:
56
+ return dumps(self)
57
+
58
+ def _custom_handle(value: typing.Any) -> typing.Union[typing.Dict[str, typing.Any], str]:
59
+ """
60
+ Handle objects that are not JSON serializable by default,
61
+ e.g., calling vars() on an object.
62
+ """
63
+
64
+ if (isinstance(value, DictConverter)):
65
+ return value.to_dict()
66
+
67
+ if (isinstance(value, enum.Enum)):
68
+ return str(value)
69
+
70
+ if (hasattr(value, '__dict__')):
71
+ return vars(value)
72
+
73
+ raise ValueError(f"Could not JSON serialize object: '{value}'.")
74
+
14
75
  def load(file_obj: typing.TextIO, strict: bool = False, **kwargs) -> typing.Dict[str, typing.Any]:
15
76
  """
16
77
  Load a file object/handler as JSON.
@@ -52,29 +113,51 @@ def load_path(
52
113
  except Exception as ex:
53
114
  raise ValueError(f"Failed to read JSON file '{path}'.") from ex
54
115
 
116
+ def loads_object(text: str, cls: typing.Type[DictConverter], **kwargs) -> DictConverter:
117
+ """ Load a JSON string into an object (which is a subclass of DictConverter). """
118
+
119
+ data = loads(text, **kwargs)
120
+ if (not isinstance(data, dict)):
121
+ raise ValueError(f"JSON to load into an object is not a dict, found '{type(data)}'.")
122
+
123
+ return cls.from_dict(data)
124
+
125
+ def load_object_path(path: str, cls: typing.Type[DictConverter], **kwargs) -> DictConverter:
126
+ """ Load a JSON file into an object (which is a subclass of DictConverter). """
127
+
128
+ data = load_path(path, **kwargs)
129
+ if (not isinstance(data, dict)):
130
+ raise ValueError(f"JSON to load into an object is not a dict, found '{type(data)}'.")
131
+
132
+ return cls.from_dict(data)
133
+
55
134
  def dump(
56
135
  data: typing.Any,
57
136
  file_obj: typing.TextIO,
137
+ default: typing.Union[typing.Callable, None] = _custom_handle,
58
138
  sort_keys: bool = True,
59
139
  **kwargs) -> None:
60
140
  """ Dump an object as a JSON file object. """
61
141
 
62
- json.dump(data, file_obj, sort_keys = sort_keys, **kwargs)
142
+ json.dump(data, file_obj, default = default, sort_keys = sort_keys, **kwargs)
63
143
 
64
144
  def dumps(
65
145
  data: typing.Any,
146
+ default: typing.Union[typing.Callable, None] = _custom_handle,
66
147
  sort_keys: bool = True,
67
148
  **kwargs) -> str:
68
149
  """ Dump an object as a JSON string. """
69
150
 
70
- return json.dumps(data, sort_keys = sort_keys, **kwargs)
151
+ return json.dumps(data, default = default, sort_keys = sort_keys, **kwargs)
71
152
 
72
153
  def dump_path(
73
154
  data: typing.Any,
74
155
  path: str,
156
+ default: typing.Union[typing.Callable, None] = _custom_handle,
157
+ sort_keys: bool = True,
75
158
  encoding: str = edq.util.dirent.DEFAULT_ENCODING,
76
159
  **kwargs) -> None:
77
160
  """ Dump an object as a JSON file. """
78
161
 
79
162
  with open(path, 'w', encoding = encoding) as file:
80
- dump(data, file, **kwargs)
163
+ dump(data, file, default = default, sort_keys = sort_keys, **kwargs)
edq/util/json_test.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import typing
2
3
 
3
4
  import edq.testing.unittest
4
5
  import edq.util.dirent
@@ -80,10 +81,10 @@ class TestJSON(edq.testing.unittest.BaseTest):
80
81
  def _subtest_loads_dumps(self, text_content, dict_content, strict):
81
82
  actual_dict = edq.util.json.loads(text_content, strict = strict)
82
83
  actual_text = edq.util.json.dumps(dict_content)
83
- double_conversion_dict = edq.util.json.dumps(actual_dict)
84
+ double_conversion_text = edq.util.json.dumps(actual_dict)
84
85
 
85
86
  self.assertDictEqual(dict_content, actual_dict)
86
- self.assertEqual(actual_text, double_conversion_dict)
87
+ self.assertEqual(actual_text, double_conversion_text)
87
88
 
88
89
  def _subtest_load_dump(self, text_content, dict_content, strict):
89
90
  temp_dir = edq.util.dirent.get_temp_dir(prefix = 'edq_test_json_')
@@ -119,3 +120,109 @@ class TestJSON(edq.testing.unittest.BaseTest):
119
120
 
120
121
  self.assertDictEqual(dict_content, text_load)
121
122
  self.assertDictEqual(dict_load, text_load)
123
+
124
+ def test_object_base(self):
125
+ """
126
+ Test loading and dumping JSON objects
127
+ """
128
+
129
+ # [(string, object, error_substring), ...]
130
+ test_cases = [
131
+ # Base
132
+ (
133
+ '{"a": 1, "b": "b"}',
134
+ _TestConverter(1, "b"),
135
+ None,
136
+ ),
137
+
138
+ # Missing Key
139
+ (
140
+ '{"a": 1}',
141
+ _TestConverter(1, None),
142
+ None,
143
+ ),
144
+
145
+ # Empty
146
+ (
147
+ '{}',
148
+ _TestConverter(None, None),
149
+ None,
150
+ ),
151
+
152
+ # Extra Key
153
+ (
154
+ '{"a": 1, "b": "b", "c": 0}',
155
+ _TestConverter(1, "b"),
156
+ None,
157
+ ),
158
+
159
+ # List
160
+ (
161
+ '[{"a": 1, "b": "b"}]',
162
+ _TestConverter(1, "b"),
163
+ 'not a dict',
164
+ ),
165
+ ]
166
+
167
+ # [(function, name), ...]
168
+ test_methods = [
169
+ (self._subtest_loads_object, 'subtest_loads_object'),
170
+ (self._subtest_load_object_path, 'subtest_load_object_path'),
171
+ ]
172
+
173
+ for (test_method, test_method_name) in test_methods:
174
+ for (i, test_case) in enumerate(test_cases):
175
+ (text_content, object_content, error_substring) = test_case
176
+
177
+ with self.subTest(msg = f"Subtest {test_method_name}, Case {i} ('{text_content}'):"):
178
+ try:
179
+ test_method(text_content, object_content)
180
+ except AssertionError:
181
+ # The subttest failed an assertion.
182
+ raise
183
+ except Exception as ex:
184
+ error_string = self.format_error_string(ex)
185
+ if (error_substring is None):
186
+ self.fail(f"Unexpected error: '{error_string}'.")
187
+
188
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
189
+
190
+ continue
191
+
192
+ if (error_substring is not None):
193
+ self.fail(f"Did not get expected error: '{error_substring}'.")
194
+
195
+ def _subtest_loads_object(self, text_content, object_content):
196
+ actual_object = edq.util.json.loads_object(text_content, _TestConverter)
197
+ actual_text = edq.util.json.dumps(object_content)
198
+ double_conversion_text = edq.util.json.dumps(actual_object)
199
+
200
+ self.assertEqual(object_content, actual_object)
201
+ self.assertEqual(actual_text, double_conversion_text)
202
+
203
+ def _subtest_load_object_path(self, text_content, object_content):
204
+ temp_dir = edq.util.dirent.get_temp_dir(prefix = 'edq_test_json_object_path_')
205
+
206
+ path_text = os.path.join(temp_dir, 'test-text.json')
207
+ path_object = os.path.join(temp_dir, 'test-object.json')
208
+
209
+ edq.util.dirent.write_file(path_text, text_content)
210
+ text_load = edq.util.json.load_object_path(path_text, _TestConverter)
211
+
212
+ edq.util.json.dump_path(object_content, path_object)
213
+ object_load = edq.util.json.load_object_path(path_object, _TestConverter)
214
+
215
+ self.assertEqual(object_content, text_load)
216
+ self.assertEqual(object_load, text_load)
217
+
218
+ class _TestConverter(edq.util.json.DictConverter):
219
+ def __init__(self, a: typing.Union[int, None] = None, b: typing.Union[str, None] = None, **kwargs) -> None:
220
+ self.a: typing.Union[int, None] = a
221
+ self.b: typing.Union[str, None] = b
222
+
223
+ def to_dict(self) -> typing.Dict[str, typing.Any]:
224
+ return vars(self)
225
+
226
+ @classmethod
227
+ def from_dict(cls, data: typing.Dict[str, typing.Any]) -> typing.Any:
228
+ return _TestConverter(**data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edq-utils
3
- Version: 0.0.2
3
+ Version: 0.0.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
@@ -37,6 +37,7 @@ License-File: LICENSE
37
37
  Requires-Dist: json5>=0.9.14
38
38
  Provides-Extra: dev
39
39
  Requires-Dist: mypy>=1.14.1; extra == "dev"
40
+ Requires-Dist: pdoc>=14.7.0; extra == "dev"
40
41
  Requires-Dist: pylint; extra == "dev"
41
42
  Requires-Dist: twine; extra == "dev"
42
43
  Requires-Dist: vermin; extra == "dev"
@@ -1,11 +1,13 @@
1
- edq/__init__.py,sha256=miSsqPy2PFJ18TW9kocQJLcv4HFpkfQhcTzgAO-GTVE,22
2
- edq/testing/run.py,sha256=pibKFJDHCtGoKyuN7i95ZGjwuXV5jL-XPy4W-l14X2Q,3150
3
- edq/testing/unittest.py,sha256=WIcc5psTM-e6NQbQ3ajBjSRRRHBqvMuKOvK_pZMcTlk,1341
4
- edq/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- edq/util/dirent.py,sha256=LR-ePtQZKEWNfZkyJ-zxFdcggQKYZ8fT379mcBYeepw,9412
6
- edq/util/dirent_test.py,sha256=FSRqhp18S4-7J9XroLxPUoJMKPBDpp3PfPKraM_7atw,30037
7
- edq/util/json.py,sha256=YRUrQvLvwDTQBcURGk6nGJLd9m6TB_jdgdrYPLElcyk,2132
8
- edq/util/json_test.py,sha256=a0my-tdo1NoPv8wWwxk2pIw56CS7QyOrODkGx2-GO1c,4273
1
+ edq/__init__.py,sha256=SgI3o5Z-z1KSX-sg-2ZL2vhNQ-KiMte0ewCvjQv8wWA,86
2
+ edq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ edq/testing/__init__.py,sha256=IKd3fPU_8d_jP19HxG-zKwxFwn7nqFGGtXOY5slY41c,32
4
+ edq/testing/run.py,sha256=qnqGCEwZP-hY87X99EJw2xoMaTF8QeOf7Lx8JI7oM_4,3843
5
+ edq/testing/unittest.py,sha256=J_sUYGIdP8HDQioQtUPF_H4n2aKf0_XHLdhzs59fvck,1665
6
+ edq/util/__init__.py,sha256=9EFKQE77S-B6OJJKFaMg8k3WkMMUQYlGjlTv6tQmWVo,29
7
+ edq/util/dirent.py,sha256=C-ZTVbOVBlEc71g4l8rMO5EJHv4Lcl9-FH0Kp1B3oc8,10314
8
+ edq/util/dirent_test.py,sha256=oXpAaEhOkEr0zw0fdAPypx7QH1aFbY2Hpox-9OpZjhs,33380
9
+ edq/util/json.py,sha256=p74F5OCxbRv4mvMSkRdy-fYVqAETx5_65IEhesTH8SM,5228
10
+ edq/util/json_test.py,sha256=utUVRbw3z42ke4fpRVI294RrFHcMKms8khVYRkISNk4,8009
9
11
  edq/util/pyimport.py,sha256=M1j58vg4b6gTg92Cz5-bns3eQCCIMKDApBclP-iR620,2198
10
12
  edq/util/pyimport_test.py,sha256=wuTR5pzVZanWDA2FuVc-Pxyo_GwkGGfFf_qyK6LNQRs,2851
11
13
  edq/util/reflection.py,sha256=jPcW6h0fwSDYh04O5rUxlgoF7HK6fVQ2mq7DD9qPrEg,972
@@ -19,8 +21,8 @@ edq/util/testdata/dirent-operations/dir_1/b.txt,sha256=AmOCmYm2_ZVPcrqvL8ZLwuLwH
19
21
  edq/util/testdata/dirent-operations/dir_1/dir_2/c.txt,sha256=o6XnFfDMV0pzw_m-u2vCTzL_1bZ7OHJEwskJ2neaFHg,2
20
22
  edq/util/testdata/dirent-operations/symlink_dir_1/b.txt,sha256=AmOCmYm2_ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8,2
21
23
  edq/util/testdata/dirent-operations/symlink_dir_1/dir_2/c.txt,sha256=o6XnFfDMV0pzw_m-u2vCTzL_1bZ7OHJEwskJ2neaFHg,2
22
- edq_utils-0.0.2.dist-info/licenses/LICENSE,sha256=MS4iYEl4rOxMoprZuc86iYVoyk4YgaVoMt7WmGvVF8w,1064
23
- edq_utils-0.0.2.dist-info/METADATA,sha256=WczGh7XL8QbJ0OLaLPE0WyHA65YucW8UAv0LG6TN7s8,2427
24
- edq_utils-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- edq_utils-0.0.2.dist-info/top_level.txt,sha256=znBHSj6tgXtcMKrUVtovLli5fIEJCb7d-BMxTLRK4zk,4
26
- edq_utils-0.0.2.dist-info/RECORD,,
24
+ edq_utils-0.0.3.dist-info/licenses/LICENSE,sha256=MS4iYEl4rOxMoprZuc86iYVoyk4YgaVoMt7WmGvVF8w,1064
25
+ edq_utils-0.0.3.dist-info/METADATA,sha256=YoOLw86gdPLaSAy_z9UfftRk83wxjjM-nD7r8H2FsEg,2471
26
+ edq_utils-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
+ edq_utils-0.0.3.dist-info/top_level.txt,sha256=znBHSj6tgXtcMKrUVtovLli5fIEJCb7d-BMxTLRK4zk,4
28
+ edq_utils-0.0.3.dist-info/RECORD,,