edq-utils 0.0.2__py3-none-any.whl → 0.0.4__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/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,7 +13,66 @@ import json5
11
13
 
12
14
  import edq.util.dirent
13
15
 
14
- def load(file_obj: typing.TextIO, strict: bool = False, **kwargs) -> typing.Dict[str, typing.Any]:
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 bool(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 dict(vars(value))
72
+
73
+ raise ValueError(f"Could not JSON serialize object: '{value}'.")
74
+
75
+ def load(file_obj: typing.TextIO, strict: bool = False, **kwargs: typing.Any) -> typing.Any:
15
76
  """
16
77
  Load a file object/handler as JSON.
17
78
  If strict is set, then use standard Python JSON,
@@ -23,7 +84,7 @@ def load(file_obj: typing.TextIO, strict: bool = False, **kwargs) -> typing.Dict
23
84
 
24
85
  return json5.load(file_obj, **kwargs)
25
86
 
26
- def loads(text: str, strict: bool = False, **kwargs) -> typing.Dict[str, typing.Any]:
87
+ def loads(text: str, strict: bool = False, **kwargs: typing.Any) -> typing.Any:
27
88
  """
28
89
  Load a string as JSON.
29
90
  If strict is set, then use standard Python JSON,
@@ -39,7 +100,7 @@ def load_path(
39
100
  path: str,
40
101
  strict: bool = False,
41
102
  encoding: str = edq.util.dirent.DEFAULT_ENCODING,
42
- **kwargs) -> typing.Dict[str, typing.Any]:
103
+ **kwargs: typing.Any) -> typing.Any:
43
104
  """
44
105
  Load a file path as JSON.
45
106
  If strict is set, then use standard Python 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: typing.Any) -> 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) # type: ignore[no-any-return]
124
+
125
+ def load_object_path(path: str, cls: typing.Type[DictConverter], **kwargs: typing.Any) -> 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) # type: ignore[no-any-return]
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
- **kwargs) -> None:
139
+ **kwargs: typing.Any) -> 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
- **kwargs) -> str:
148
+ **kwargs: typing.Any) -> 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
- **kwargs) -> None:
159
+ **kwargs: typing.Any) -> 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)
edq/util/pyimport.py CHANGED
@@ -71,3 +71,24 @@ def import_name(module_name: str, cache: bool = True) -> typing.Any:
71
71
  _import_cache[cache_key] = module
72
72
 
73
73
  return module
74
+
75
+ def fetch(name: str) -> typing.Any:
76
+ """
77
+ Fetch an entity inside of a module.
78
+ Note that the target is not a module, but an attribute/object inside of the module.
79
+ The provided name should be fully qualified.
80
+ """
81
+
82
+ parts = name.strip().rsplit('.', 1)
83
+ if (len(parts) != 2):
84
+ raise ValueError(f"Target name of fetch must be fully qualified, got '{name}'.")
85
+
86
+ module_name = parts[0]
87
+ short_name = parts[1]
88
+
89
+ module = import_name(module_name)
90
+
91
+ if (not hasattr(module, short_name)):
92
+ raise ValueError(f"Module '{module_name}' does not have attribute '{short_name}'.")
93
+
94
+ return getattr(module, short_name)
edq/util/pyimport_test.py CHANGED
@@ -81,3 +81,39 @@ class TestPyImport(edq.testing.unittest.BaseTest):
81
81
  self.fail(f"Did not get expected error: '{error_substring}'.")
82
82
 
83
83
  self.assertIsNotNone(module)
84
+
85
+ def test_fetch_base(self):
86
+ """ Test fetching an attribute from a module. """
87
+
88
+ # [(name, error substring), ...]
89
+ test_cases = [
90
+ # Standard Module
91
+ ('edq.util.pyimport.fetch', None),
92
+
93
+ # Errors
94
+ ('', 'Target name of fetch must be fully qualified'),
95
+ ('edq', 'Target name of fetch must be fully qualified'),
96
+ ('ZZZ.aaa', 'Unable to locate module'),
97
+ ('edq.ZZZ.aaa', 'Unable to locate module'),
98
+ ('edq.util.pyimport.ZZZ', 'does not have attribute'),
99
+ ]
100
+
101
+ for (i, test_case) in enumerate(test_cases):
102
+ (name, error_substring) = test_case
103
+
104
+ with self.subTest(msg = f"Case {i} ('{name}'):"):
105
+ try:
106
+ target = edq.util.pyimport.fetch(name)
107
+ except Exception as ex:
108
+ error_string = self.format_error_string(ex)
109
+ if (error_substring is None):
110
+ self.fail(f"Unexpected error: '{error_string}'.")
111
+
112
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
113
+
114
+ continue
115
+
116
+ if (error_substring is not None):
117
+ self.fail(f"Did not get expected error: '{error_substring}'.")
118
+
119
+ self.assertIsNotNone(target)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edq-utils
3
- Version: 0.0.2
3
+ Version: 0.0.4
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"
@@ -0,0 +1,41 @@
1
+ edq/__init__.py,sha256=POoiN2MIpiTQijlzxyJCeYpx1EkkrGbcPzAy_Raps7A,86
2
+ edq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ edq/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ edq/cli/version.py,sha256=hEz8DonP6cmDMiNPpUT4V4hbSQhcC7z6NhJSxqVY6NY,592
5
+ edq/cli/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ edq/cli/testing/cli-test.py,sha256=1hjTnAjOKZu69yBBjsO4dn0m9ApF26luo2huSZr5_Yo,1048
7
+ edq/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ edq/core/argparser.py,sha256=LWv4EYlgmqEw0jscpV_-aKSobBEDAZ02Zv3cF2JArx8,4180
9
+ edq/core/argparser_test.py,sha256=YzNft7c1Nk5tQzHEW7nEuJakjvk1IZiYimaQ_6HI6Bo,4046
10
+ edq/core/log.py,sha256=Aq5-Tznq5fgCfU0OI4ECy2OnCoiwV4bTFkhXXdLoG7Q,3726
11
+ edq/core/version.py,sha256=cCadcn3M4qoxbQRu4WRHXUu_xl49GTGSDpr6hpr7Vxw,124
12
+ edq/testing/__init__.py,sha256=IKd3fPU_8d_jP19HxG-zKwxFwn7nqFGGtXOY5slY41c,32
13
+ edq/testing/asserts.py,sha256=kxNdAC3usbOBQXuagxUNv8IDQ57I3Moe4eWc51xaNIo,2524
14
+ edq/testing/cli.py,sha256=u1GfgYz4_1oEKftCjrrg-gMQZpj_ry1hXvec2OQQA2M,9728
15
+ edq/testing/cli_test.py,sha256=l-CPBXpiMb12_qgQj5FoXHM8GJBKnXieogrCb191Dlc,224
16
+ edq/testing/run.py,sha256=qnqGCEwZP-hY87X99EJw2xoMaTF8QeOf7Lx8JI7oM_4,3843
17
+ edq/testing/unittest.py,sha256=ysv_XTL14sDRf4EVx6lRN44A0InQSh-dYeG3bTJ9dzU,1751
18
+ edq/testing/testdata/cli/tests/version_base.txt,sha256=5e9oSQaivA-0Th7wXpNRhACluyDwvFTL_1xfAkS2Pko,69
19
+ edq/util/__init__.py,sha256=9EFKQE77S-B6OJJKFaMg8k3WkMMUQYlGjlTv6tQmWVo,29
20
+ edq/util/dirent.py,sha256=YDgHW9xcYzid1rxsk3LowIBXW-JIokdN5SXaVSTFt4U,10334
21
+ edq/util/dirent_test.py,sha256=oXpAaEhOkEr0zw0fdAPypx7QH1aFbY2Hpox-9OpZjhs,33380
22
+ edq/util/json.py,sha256=IOTJqZGFYWgng7D1NMd6NDbbmOuw8C4Mpo6-2w1JFNY,5344
23
+ edq/util/json_test.py,sha256=utUVRbw3z42ke4fpRVI294RrFHcMKms8khVYRkISNk4,8009
24
+ edq/util/pyimport.py,sha256=26OIuCXELyqtwlooMqDEs4GJQrkrAgxnXNYTlqqtsBY,2852
25
+ edq/util/pyimport_test.py,sha256=Xno0MIa3yMTfBfoTgjKCIMpr1ZShU6bvo9rBRdecXQU,4202
26
+ edq/util/reflection.py,sha256=jPcW6h0fwSDYh04O5rUxlgoF7HK6fVQ2mq7DD9qPrEg,972
27
+ edq/util/time.py,sha256=anoNM_KniARLombv2BnsoHuCzDqMKiDdIzV7RUe2ZOk,2648
28
+ edq/util/time_test.py,sha256=iQZwzVTVQQ4TdXrLb9MUMCYlKrIe8qyF-hiC9YLTaMo,4610
29
+ edq/util/testdata/dirent-operations/a.txt,sha256=h0KPxSKAPTEGXnvOPPA_5HUJZjHl4Hu9eg_eYMTPJcc,2
30
+ edq/util/testdata/dirent-operations/file_empty,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ edq/util/testdata/dirent-operations/symlink_a.txt,sha256=h0KPxSKAPTEGXnvOPPA_5HUJZjHl4Hu9eg_eYMTPJcc,2
32
+ edq/util/testdata/dirent-operations/symlink_file_empty,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ edq/util/testdata/dirent-operations/dir_1/b.txt,sha256=AmOCmYm2_ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8,2
34
+ edq/util/testdata/dirent-operations/dir_1/dir_2/c.txt,sha256=o6XnFfDMV0pzw_m-u2vCTzL_1bZ7OHJEwskJ2neaFHg,2
35
+ edq/util/testdata/dirent-operations/symlink_dir_1/b.txt,sha256=AmOCmYm2_ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8,2
36
+ edq/util/testdata/dirent-operations/symlink_dir_1/dir_2/c.txt,sha256=o6XnFfDMV0pzw_m-u2vCTzL_1bZ7OHJEwskJ2neaFHg,2
37
+ edq_utils-0.0.4.dist-info/licenses/LICENSE,sha256=MS4iYEl4rOxMoprZuc86iYVoyk4YgaVoMt7WmGvVF8w,1064
38
+ edq_utils-0.0.4.dist-info/METADATA,sha256=Pt_CQ94UC8ePrD8iEFGSSltVQUJPh8h9-d3Pt_Ll1RE,2471
39
+ edq_utils-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
+ edq_utils-0.0.4.dist-info/top_level.txt,sha256=znBHSj6tgXtcMKrUVtovLli5fIEJCb7d-BMxTLRK4zk,4
41
+ edq_utils-0.0.4.dist-info/RECORD,,
@@ -1,26 +0,0 @@
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
9
- edq/util/pyimport.py,sha256=M1j58vg4b6gTg92Cz5-bns3eQCCIMKDApBclP-iR620,2198
10
- edq/util/pyimport_test.py,sha256=wuTR5pzVZanWDA2FuVc-Pxyo_GwkGGfFf_qyK6LNQRs,2851
11
- edq/util/reflection.py,sha256=jPcW6h0fwSDYh04O5rUxlgoF7HK6fVQ2mq7DD9qPrEg,972
12
- edq/util/time.py,sha256=anoNM_KniARLombv2BnsoHuCzDqMKiDdIzV7RUe2ZOk,2648
13
- edq/util/time_test.py,sha256=iQZwzVTVQQ4TdXrLb9MUMCYlKrIe8qyF-hiC9YLTaMo,4610
14
- edq/util/testdata/dirent-operations/a.txt,sha256=h0KPxSKAPTEGXnvOPPA_5HUJZjHl4Hu9eg_eYMTPJcc,2
15
- edq/util/testdata/dirent-operations/file_empty,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- edq/util/testdata/dirent-operations/symlink_a.txt,sha256=h0KPxSKAPTEGXnvOPPA_5HUJZjHl4Hu9eg_eYMTPJcc,2
17
- edq/util/testdata/dirent-operations/symlink_file_empty,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- edq/util/testdata/dirent-operations/dir_1/b.txt,sha256=AmOCmYm2_ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8,2
19
- edq/util/testdata/dirent-operations/dir_1/dir_2/c.txt,sha256=o6XnFfDMV0pzw_m-u2vCTzL_1bZ7OHJEwskJ2neaFHg,2
20
- edq/util/testdata/dirent-operations/symlink_dir_1/b.txt,sha256=AmOCmYm2_ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8,2
21
- 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,,