dir-content-diff 1.6.0__tar.gz → 1.8.0__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.
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.copier-answers.yml +1 -1
- dir-content-diff-1.8.0/.coveragerc_py38 +11 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/workflows/commitlint.yml +1 -1
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/workflows/run-tox.yml +2 -1
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.pylintrc +1 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/CHANGELOG.md +17 -0
- {dir-content-diff-1.6.0/dir_content_diff.egg-info → dir-content-diff-1.8.0}/PKG-INFO +19 -2
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/README.md +12 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff/__init__.py +25 -2
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff/base_comparators.py +85 -4
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff/pandas.py +39 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0/dir_content_diff.egg-info}/PKG-INFO +19 -2
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff.egg-info/SOURCES.txt +1 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff.egg-info/requires.txt +6 -1
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/setup.py +4 -1
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/tests/conftest.py +6 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/tests/test_base.py +187 -14
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/tests/test_pandas.py +211 -26
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/tests/test_pytest_plugin.py +22 -15
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/tox.ini +1 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.auto-changelog +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.auto-changelog-template.hbs +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.codespellignorelines +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.codespellrc +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.coveragerc +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.flake8 +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.gitattributes +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/ISSUE_TEMPLATE/how_to_use.yaml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/dependabot.yml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/pull_request_template.md +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.github/workflows/publish-sdist.yml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.gitignore +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.gitreview +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.pre-commit-config.yaml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/.readthedocs.yml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/AUTHORS.md +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/CONTRIBUTING.md +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/LICENSE.txt +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/MANIFEST.in +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/codecov.yml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/commitlint.config.js +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff/pytest_plugin.py +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff/util.py +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff.egg-info/dependency_links.txt +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff.egg-info/entry_points.txt +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/dir_content_diff.egg-info/top_level.txt +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/docs/Makefile +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/docs/source/.gitignore +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/docs/source/api_ref.rst +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/docs/source/changelog.rst +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/docs/source/conf.py +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/docs/source/contributing.rst +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/docs/source/index.rst +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/package.json +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/pyproject.toml +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/setup.cfg +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/tests/__init__.py +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/tests/generate_test_files.py +0 -0
- {dir-content-diff-1.6.0 → dir-content-diff-1.8.0}/tests/test_version.py +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[paths]
|
|
2
|
+
source_paths =
|
|
3
|
+
dir_content_diff
|
|
4
|
+
*/site-packages/dir_content_diff
|
|
5
|
+
*/dir_content_diff/dir_content_diff
|
|
6
|
+
*/dir-content-diff/dir_content_diff
|
|
7
|
+
|
|
8
|
+
[report]
|
|
9
|
+
exclude_also =
|
|
10
|
+
return pd\.read_feather\(path, \*\*kwargs\)
|
|
11
|
+
data\.to_feather\(path, \*\*kwargs\)
|
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
- uses: actions/checkout@v4
|
|
13
13
|
- uses: actions/setup-node@v4
|
|
14
14
|
with:
|
|
15
|
-
node-version: '
|
|
15
|
+
node-version: '20'
|
|
16
16
|
- run: npm install -g --force commitlint @commitlint/cli commitlint-plugin-cleanfeet
|
|
17
17
|
- run: npm install conventional-changelog-conventionalcommits
|
|
18
18
|
- run: touch .git/COMMIT_EDITMSG
|
|
@@ -64,12 +64,13 @@ jobs:
|
|
|
64
64
|
with:
|
|
65
65
|
report_paths: 'reports/pytest-*.xml'
|
|
66
66
|
- name: Upload to codecov
|
|
67
|
-
uses: codecov/codecov-action@
|
|
67
|
+
uses: codecov/codecov-action@v4
|
|
68
68
|
with:
|
|
69
69
|
fail_ci_if_error: false
|
|
70
70
|
directory: ./reports
|
|
71
71
|
flags: pytest
|
|
72
72
|
name: "dir-content-diff-py${{ matrix.python-version }}-${{ matrix.min_versions }}"
|
|
73
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
73
74
|
- name: Upload test artifacts
|
|
74
75
|
uses: actions/upload-artifact@v4
|
|
75
76
|
if: always()
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.0](https://github.com/BlueBrain/dir-content-diff/compare/1.7.0..1.8.0)
|
|
4
|
+
|
|
5
|
+
> 8 April 2024
|
|
6
|
+
|
|
7
|
+
### New Features
|
|
8
|
+
|
|
9
|
+
- Add new comparators for Feather, Parquet and Stata files (Adrien Berchet - [#56](https://github.com/BlueBrain/dir-content-diff/pull/56))
|
|
10
|
+
|
|
11
|
+
## [1.7.0](https://github.com/BlueBrain/dir-content-diff/compare/1.6.0..1.7.0)
|
|
12
|
+
|
|
13
|
+
> 5 February 2024
|
|
14
|
+
|
|
15
|
+
### New Features
|
|
16
|
+
|
|
17
|
+
- Add 'replace_pattern' entry for DictComparator and several related save capabilities (Adrien Berchet - [#53](https://github.com/BlueBrain/dir-content-diff/pull/53))
|
|
18
|
+
- Add 'patterns' entry in specific_args (Adrien Berchet - [#52](https://github.com/BlueBrain/dir-content-diff/pull/52))
|
|
19
|
+
|
|
3
20
|
## [1.6.0](https://github.com/BlueBrain/dir-content-diff/compare/1.5.0..1.6.0)
|
|
4
21
|
|
|
5
22
|
> 29 January 2024
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dir-content-diff
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Simple tool to compare directory contents.
|
|
5
5
|
Home-page: https://dir-content-diff.readthedocs.io
|
|
6
6
|
Author: Blue Brain Project, EPFL
|
|
@@ -22,25 +22,30 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
License-File: LICENSE.txt
|
|
23
23
|
License-File: AUTHORS.md
|
|
24
24
|
Requires-Dist: dictdiffer>=0.8
|
|
25
|
+
Requires-Dist: dicttoxml>=1.7.12
|
|
25
26
|
Requires-Dist: diff_pdf_visually>=1.7
|
|
27
|
+
Requires-Dist: jsonpath-ng>=1.5
|
|
26
28
|
Requires-Dist: PyYaml>=6
|
|
27
29
|
Provides-Extra: pandas
|
|
28
30
|
Requires-Dist: pandas>=1.4; extra == "pandas"
|
|
31
|
+
Requires-Dist: pyarrow>=11; extra == "pandas"
|
|
29
32
|
Requires-Dist: tables>=3.7; extra == "pandas"
|
|
30
33
|
Provides-Extra: docs
|
|
31
34
|
Requires-Dist: m2r2; extra == "docs"
|
|
32
35
|
Requires-Dist: sphinx; extra == "docs"
|
|
33
36
|
Requires-Dist: sphinx-bluebrain-theme; extra == "docs"
|
|
34
37
|
Requires-Dist: pandas>=1.4; extra == "docs"
|
|
38
|
+
Requires-Dist: pyarrow>=11; extra == "docs"
|
|
35
39
|
Requires-Dist: tables>=3.7; extra == "docs"
|
|
36
40
|
Provides-Extra: test
|
|
37
|
-
Requires-Dist: coverage>=
|
|
41
|
+
Requires-Dist: coverage>=7.2; extra == "test"
|
|
38
42
|
Requires-Dist: dicttoxml>=1.7.16; extra == "test"
|
|
39
43
|
Requires-Dist: matplotlib>=3; extra == "test"
|
|
40
44
|
Requires-Dist: rst2pdf>=0.99; extra == "test"
|
|
41
45
|
Requires-Dist: pytest>=6.2; extra == "test"
|
|
42
46
|
Requires-Dist: pytest-html<4,>=2; extra == "test"
|
|
43
47
|
Requires-Dist: pandas>=1.4; extra == "test"
|
|
48
|
+
Requires-Dist: pyarrow>=11; extra == "test"
|
|
44
49
|
Requires-Dist: tables>=3.7; extra == "test"
|
|
45
50
|
|
|
46
51
|
[](https://github.com/BlueBrain/dir-content-diff/releases)
|
|
@@ -208,6 +213,18 @@ specific_args = {
|
|
|
208
213
|
}
|
|
209
214
|
```
|
|
210
215
|
|
|
216
|
+
And last but not least, it's possible to use regular expressions to associate specific arguments to
|
|
217
|
+
a set of files:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
specific_args = {
|
|
221
|
+
"all files with *.a of *.b extensions": {
|
|
222
|
+
"patterns": [r".*\.[a,b]$"],
|
|
223
|
+
"comparator": dir_content_diff.BaseComparator(),
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
211
228
|
|
|
212
229
|
### Export formatted data
|
|
213
230
|
|
|
@@ -163,6 +163,18 @@ specific_args = {
|
|
|
163
163
|
}
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
+
And last but not least, it's possible to use regular expressions to associate specific arguments to
|
|
167
|
+
a set of files:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
specific_args = {
|
|
171
|
+
"all files with *.a of *.b extensions": {
|
|
172
|
+
"patterns": [r".*\.[a,b]$"],
|
|
173
|
+
"comparator": dir_content_diff.BaseComparator(),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
166
178
|
|
|
167
179
|
### Export formatted data
|
|
168
180
|
|
|
@@ -5,6 +5,7 @@ Simple tool to compare directory contents.
|
|
|
5
5
|
import copy
|
|
6
6
|
import importlib.metadata
|
|
7
7
|
import logging
|
|
8
|
+
import re
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
from dir_content_diff.base_comparators import DefaultComparator
|
|
@@ -240,9 +241,19 @@ def compare_trees(
|
|
|
240
241
|
kwarg_name_2: kwarg_value_2,
|
|
241
242
|
}
|
|
242
243
|
},
|
|
243
|
-
<another_file_path>: {...}
|
|
244
|
+
<another_file_path>: {...},
|
|
245
|
+
<a name for this category>: {
|
|
246
|
+
"patterns": ["regex1", "regex2", ...],
|
|
247
|
+
... (other arguments)
|
|
248
|
+
}
|
|
244
249
|
}
|
|
245
250
|
|
|
251
|
+
If the "patterns" entry is present, then the name is not considered and is only used as
|
|
252
|
+
a helper for the user. When a "patterns" entry is detected, the other arguments are
|
|
253
|
+
applied to all files whose relative name matches one of the given regular expression
|
|
254
|
+
patterns. If a file could match multiple patterns of different groups, only the first
|
|
255
|
+
one is considered.
|
|
256
|
+
|
|
246
257
|
Note that all entries in this ``dict`` are optional.
|
|
247
258
|
return_raw_diffs (bool): If set to ``True``, only the raw differences are returned instead
|
|
248
259
|
of a formatted report.
|
|
@@ -275,6 +286,11 @@ def compare_trees(
|
|
|
275
286
|
else:
|
|
276
287
|
specific_args = copy.deepcopy(specific_args)
|
|
277
288
|
|
|
289
|
+
pattern_specific_args = {}
|
|
290
|
+
for v in specific_args.values():
|
|
291
|
+
for pattern in v.pop("patterns", []):
|
|
292
|
+
pattern_specific_args[re.compile(pattern)] = v
|
|
293
|
+
|
|
278
294
|
# Loop over all files and call the correct comparator
|
|
279
295
|
different_files = {}
|
|
280
296
|
for ref_file in ref_path.glob("**/*"):
|
|
@@ -285,7 +301,14 @@ def compare_trees(
|
|
|
285
301
|
comp_file = comp_path / relative_path
|
|
286
302
|
|
|
287
303
|
if comp_file.exists():
|
|
288
|
-
specific_file_args = specific_args.get(relative_path,
|
|
304
|
+
specific_file_args = specific_args.get(relative_path, None)
|
|
305
|
+
if specific_file_args is None:
|
|
306
|
+
for pattern, pattern_args in pattern_specific_args.items():
|
|
307
|
+
if pattern.match(relative_path):
|
|
308
|
+
specific_file_args = copy.deepcopy(pattern_args)
|
|
309
|
+
break
|
|
310
|
+
if specific_file_args is None:
|
|
311
|
+
specific_file_args = {}
|
|
289
312
|
comparator = specific_file_args.pop(
|
|
290
313
|
"comparator",
|
|
291
314
|
comparators.get(
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
import configparser
|
|
3
3
|
import filecmp
|
|
4
4
|
import json
|
|
5
|
+
import re
|
|
5
6
|
from abc import ABC
|
|
6
7
|
from abc import abstractmethod
|
|
7
8
|
from xml.etree import ElementTree
|
|
8
9
|
|
|
9
10
|
import dictdiffer
|
|
11
|
+
import jsonpath_ng
|
|
10
12
|
import yaml
|
|
13
|
+
from dicttoxml import dicttoxml
|
|
11
14
|
from diff_pdf_visually import pdf_similar
|
|
12
15
|
|
|
13
16
|
from dir_content_diff.util import diff_msg_formatter
|
|
@@ -282,6 +285,14 @@ class DictComparator(BaseComparator):
|
|
|
282
285
|
"add": "Added the value(s) '{value}' in the '{key}' key.",
|
|
283
286
|
"change": "Changed the value of '{key}' from {value[0]} to {value[1]}.",
|
|
284
287
|
"remove": "Removed the value(s) '{value}' from '{key}' key.",
|
|
288
|
+
"missing_ref_entry": (
|
|
289
|
+
"The path '{key}' is missing in the reference dictionary, please fix the "
|
|
290
|
+
"'replace_pattern' argument."
|
|
291
|
+
),
|
|
292
|
+
"missing_comp_entry": (
|
|
293
|
+
"The path '{key}' is missing in the compared dictionary, please fix the "
|
|
294
|
+
"'replace_pattern' argument."
|
|
295
|
+
),
|
|
285
296
|
}
|
|
286
297
|
|
|
287
298
|
def __init__(self, *args, **kwargs):
|
|
@@ -318,6 +329,43 @@ class DictComparator(BaseComparator):
|
|
|
318
329
|
value[num] = str(i)
|
|
319
330
|
return value
|
|
320
331
|
|
|
332
|
+
def format_data(self, data, ref=None, replace_pattern=None, **kwargs):
|
|
333
|
+
"""Format the loaded data."""
|
|
334
|
+
# pylint: disable=too-many-nested-blocks
|
|
335
|
+
self.current_state["format_errors"] = errors = []
|
|
336
|
+
|
|
337
|
+
if replace_pattern is not None:
|
|
338
|
+
for pat, paths in replace_pattern.items():
|
|
339
|
+
pattern = pat[0]
|
|
340
|
+
new_value = pat[1]
|
|
341
|
+
count = pat[2] if len(pat) > 2 else 0
|
|
342
|
+
flags = pat[3] if len(pat) > 3 else 0
|
|
343
|
+
for raw_path in paths:
|
|
344
|
+
path = jsonpath_ng.parse(raw_path)
|
|
345
|
+
if ref is not None and len(path.find(ref)) == 0:
|
|
346
|
+
errors.append(
|
|
347
|
+
(
|
|
348
|
+
"missing_ref_entry",
|
|
349
|
+
raw_path,
|
|
350
|
+
None,
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
elif len(path.find(data)) == 0:
|
|
354
|
+
errors.append(
|
|
355
|
+
(
|
|
356
|
+
"missing_comp_entry",
|
|
357
|
+
raw_path,
|
|
358
|
+
None,
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
else:
|
|
362
|
+
for i in path.find(data):
|
|
363
|
+
if isinstance(i.value, str):
|
|
364
|
+
i.full_path.update(
|
|
365
|
+
data, re.sub(pattern, new_value, i.value, count, flags)
|
|
366
|
+
)
|
|
367
|
+
return data
|
|
368
|
+
|
|
321
369
|
def diff(self, ref, comp, *args, **kwargs):
|
|
322
370
|
"""Compare 2 dictionaries.
|
|
323
371
|
|
|
@@ -332,13 +380,16 @@ class DictComparator(BaseComparator):
|
|
|
332
380
|
path_limit (list[str]): List of path limit tuples or :class:`dictdiffer.utils.PathLimit`
|
|
333
381
|
object to limit the diff recursion depth.
|
|
334
382
|
"""
|
|
383
|
+
errors = self.current_state.get("format_errors", [])
|
|
384
|
+
|
|
335
385
|
if len(args) > 5:
|
|
336
386
|
dot_notation = args[5]
|
|
337
387
|
args = args[:5] + args[6:]
|
|
338
388
|
else:
|
|
339
389
|
dot_notation = kwargs.pop("dot_notation", False)
|
|
340
390
|
kwargs["dot_notation"] = dot_notation
|
|
341
|
-
|
|
391
|
+
errors.extend(list(dictdiffer.diff(ref, comp, *args, **kwargs)))
|
|
392
|
+
return errors
|
|
342
393
|
|
|
343
394
|
def format_diff(self, difference):
|
|
344
395
|
"""Format one element difference."""
|
|
@@ -361,6 +412,11 @@ class JsonComparator(DictComparator):
|
|
|
361
412
|
data = json.load(file)
|
|
362
413
|
return data
|
|
363
414
|
|
|
415
|
+
def save(self, data, path):
|
|
416
|
+
"""Save formatted data into a JSON file."""
|
|
417
|
+
with open(path, "w", encoding="utf-8") as file:
|
|
418
|
+
json.dump(data, file)
|
|
419
|
+
|
|
364
420
|
|
|
365
421
|
class YamlComparator(DictComparator):
|
|
366
422
|
"""Comparator for YAML files.
|
|
@@ -374,6 +430,11 @@ class YamlComparator(DictComparator):
|
|
|
374
430
|
data = yaml.full_load(file)
|
|
375
431
|
return data
|
|
376
432
|
|
|
433
|
+
def save(self, data, path):
|
|
434
|
+
"""Save formatted data into a YAML file."""
|
|
435
|
+
with open(path, "w", encoding="utf-8") as file:
|
|
436
|
+
yaml.dump(data, file)
|
|
437
|
+
|
|
377
438
|
|
|
378
439
|
class XmlComparator(DictComparator):
|
|
379
440
|
"""Comparator for XML files.
|
|
@@ -407,9 +468,14 @@ class XmlComparator(DictComparator):
|
|
|
407
468
|
data = self.xmltodict(file.read())
|
|
408
469
|
return data
|
|
409
470
|
|
|
471
|
+
def save(self, data, path):
|
|
472
|
+
"""Save formatted data into a XML file."""
|
|
473
|
+
with open(path, "w", encoding="utf-8") as file:
|
|
474
|
+
file.write(dicttoxml(data["root"]).decode())
|
|
475
|
+
|
|
410
476
|
@staticmethod
|
|
411
477
|
def _cast_from_attribute(text, attr):
|
|
412
|
-
"""
|
|
478
|
+
"""Convert XML text into a Python data format based on the tag attribute."""
|
|
413
479
|
if "type" not in attr:
|
|
414
480
|
return text
|
|
415
481
|
value_type = attr.get("type", "").lower()
|
|
@@ -453,7 +519,7 @@ class XmlComparator(DictComparator):
|
|
|
453
519
|
|
|
454
520
|
@staticmethod
|
|
455
521
|
def xmltodict(obj):
|
|
456
|
-
"""
|
|
522
|
+
"""Convert an XML string into a Python object based on each tag's attribute."""
|
|
457
523
|
root = ElementTree.fromstring(obj)
|
|
458
524
|
output = {}
|
|
459
525
|
|
|
@@ -473,11 +539,16 @@ class IniComparator(DictComparator):
|
|
|
473
539
|
"""
|
|
474
540
|
|
|
475
541
|
def load(self, path, **kwargs): # pylint: disable=arguments-differ
|
|
476
|
-
"""Open a
|
|
542
|
+
"""Open a INI file."""
|
|
477
543
|
data = configparser.ConfigParser(**kwargs)
|
|
478
544
|
data.read(path)
|
|
479
545
|
return self.configparser_to_dict(data)
|
|
480
546
|
|
|
547
|
+
def save(self, data, path):
|
|
548
|
+
"""Save formatted data into a INI file."""
|
|
549
|
+
with open(path, "w", encoding="utf-8") as file:
|
|
550
|
+
self.dict_to_configparser(data).write(file)
|
|
551
|
+
|
|
481
552
|
@staticmethod
|
|
482
553
|
def configparser_to_dict(config):
|
|
483
554
|
"""Transform a ConfigParser object into a dict."""
|
|
@@ -494,6 +565,16 @@ class IniComparator(DictComparator):
|
|
|
494
565
|
dict_config[section][option] = val
|
|
495
566
|
return dict_config
|
|
496
567
|
|
|
568
|
+
@staticmethod
|
|
569
|
+
def dict_to_configparser(data, **kwargs):
|
|
570
|
+
"""Transform a dict object into a ConfigParser."""
|
|
571
|
+
config = configparser.ConfigParser(**kwargs)
|
|
572
|
+
for k, v in data.items():
|
|
573
|
+
config.add_section(k)
|
|
574
|
+
for opt, val in v.items():
|
|
575
|
+
config[k][opt] = json.dumps(val)
|
|
576
|
+
return config
|
|
577
|
+
|
|
497
578
|
|
|
498
579
|
class PdfComparator(BaseComparator):
|
|
499
580
|
"""Comparator for PDF files."""
|
|
@@ -147,6 +147,42 @@ class HdfComparator(DataframeComparator):
|
|
|
147
147
|
data.to_hdf(path, index=index, key=key, **kwargs)
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
class FeatherComparator(DataframeComparator):
|
|
151
|
+
"""Comparator for Feather files."""
|
|
152
|
+
|
|
153
|
+
def load(self, path, **kwargs):
|
|
154
|
+
"""Load a Feather file into a :class:`pandas.DataFrame` object."""
|
|
155
|
+
return pd.read_feather(path, **kwargs)
|
|
156
|
+
|
|
157
|
+
def save(self, data, path, **kwargs):
|
|
158
|
+
"""Save data to a Feather file."""
|
|
159
|
+
data.to_feather(path, **kwargs)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class ParquetComparator(DataframeComparator):
|
|
163
|
+
"""Comparator for Parquet files."""
|
|
164
|
+
|
|
165
|
+
def load(self, path, **kwargs):
|
|
166
|
+
"""Load a Parquet file into a :class:`pandas.DataFrame` object."""
|
|
167
|
+
return pd.read_parquet(path, **kwargs)
|
|
168
|
+
|
|
169
|
+
def save(self, data, path, **kwargs):
|
|
170
|
+
"""Save data to a Parquet file."""
|
|
171
|
+
data.to_parquet(path, **kwargs)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class StataComparator(DataframeComparator):
|
|
175
|
+
"""Comparator for Stata files."""
|
|
176
|
+
|
|
177
|
+
def load(self, path, **kwargs):
|
|
178
|
+
"""Load a Stata file into a :class:`pandas.DataFrame` object."""
|
|
179
|
+
return pd.read_stata(path, **kwargs)
|
|
180
|
+
|
|
181
|
+
def save(self, data, path, **kwargs):
|
|
182
|
+
"""Save data to a Stata file."""
|
|
183
|
+
data.to_stata(path, **kwargs)
|
|
184
|
+
|
|
185
|
+
|
|
150
186
|
def register():
|
|
151
187
|
"""Register Pandas extensions."""
|
|
152
188
|
register_comparator(".csv", CsvComparator())
|
|
@@ -156,3 +192,6 @@ def register():
|
|
|
156
192
|
register_comparator(".hdf", HdfComparator())
|
|
157
193
|
register_comparator(".hdf4", HdfComparator())
|
|
158
194
|
register_comparator(".hdf5", HdfComparator())
|
|
195
|
+
register_comparator(".feather", FeatherComparator())
|
|
196
|
+
register_comparator(".parquet", ParquetComparator())
|
|
197
|
+
register_comparator(".dta", StataComparator())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dir-content-diff
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Simple tool to compare directory contents.
|
|
5
5
|
Home-page: https://dir-content-diff.readthedocs.io
|
|
6
6
|
Author: Blue Brain Project, EPFL
|
|
@@ -22,25 +22,30 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
License-File: LICENSE.txt
|
|
23
23
|
License-File: AUTHORS.md
|
|
24
24
|
Requires-Dist: dictdiffer>=0.8
|
|
25
|
+
Requires-Dist: dicttoxml>=1.7.12
|
|
25
26
|
Requires-Dist: diff_pdf_visually>=1.7
|
|
27
|
+
Requires-Dist: jsonpath-ng>=1.5
|
|
26
28
|
Requires-Dist: PyYaml>=6
|
|
27
29
|
Provides-Extra: pandas
|
|
28
30
|
Requires-Dist: pandas>=1.4; extra == "pandas"
|
|
31
|
+
Requires-Dist: pyarrow>=11; extra == "pandas"
|
|
29
32
|
Requires-Dist: tables>=3.7; extra == "pandas"
|
|
30
33
|
Provides-Extra: docs
|
|
31
34
|
Requires-Dist: m2r2; extra == "docs"
|
|
32
35
|
Requires-Dist: sphinx; extra == "docs"
|
|
33
36
|
Requires-Dist: sphinx-bluebrain-theme; extra == "docs"
|
|
34
37
|
Requires-Dist: pandas>=1.4; extra == "docs"
|
|
38
|
+
Requires-Dist: pyarrow>=11; extra == "docs"
|
|
35
39
|
Requires-Dist: tables>=3.7; extra == "docs"
|
|
36
40
|
Provides-Extra: test
|
|
37
|
-
Requires-Dist: coverage>=
|
|
41
|
+
Requires-Dist: coverage>=7.2; extra == "test"
|
|
38
42
|
Requires-Dist: dicttoxml>=1.7.16; extra == "test"
|
|
39
43
|
Requires-Dist: matplotlib>=3; extra == "test"
|
|
40
44
|
Requires-Dist: rst2pdf>=0.99; extra == "test"
|
|
41
45
|
Requires-Dist: pytest>=6.2; extra == "test"
|
|
42
46
|
Requires-Dist: pytest-html<4,>=2; extra == "test"
|
|
43
47
|
Requires-Dist: pandas>=1.4; extra == "test"
|
|
48
|
+
Requires-Dist: pyarrow>=11; extra == "test"
|
|
44
49
|
Requires-Dist: tables>=3.7; extra == "test"
|
|
45
50
|
|
|
46
51
|
[](https://github.com/BlueBrain/dir-content-diff/releases)
|
|
@@ -208,6 +213,18 @@ specific_args = {
|
|
|
208
213
|
}
|
|
209
214
|
```
|
|
210
215
|
|
|
216
|
+
And last but not least, it's possible to use regular expressions to associate specific arguments to
|
|
217
|
+
a set of files:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
specific_args = {
|
|
221
|
+
"all files with *.a of *.b extensions": {
|
|
222
|
+
"patterns": [r".*\.[a,b]$"],
|
|
223
|
+
"comparator": dir_content_diff.BaseComparator(),
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
211
228
|
|
|
212
229
|
### Export formatted data
|
|
213
230
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
dictdiffer>=0.8
|
|
2
|
+
dicttoxml>=1.7.12
|
|
2
3
|
diff_pdf_visually>=1.7
|
|
4
|
+
jsonpath-ng>=1.5
|
|
3
5
|
PyYaml>=6
|
|
4
6
|
|
|
5
7
|
[docs]
|
|
@@ -7,18 +9,21 @@ m2r2
|
|
|
7
9
|
sphinx
|
|
8
10
|
sphinx-bluebrain-theme
|
|
9
11
|
pandas>=1.4
|
|
12
|
+
pyarrow>=11
|
|
10
13
|
tables>=3.7
|
|
11
14
|
|
|
12
15
|
[pandas]
|
|
13
16
|
pandas>=1.4
|
|
17
|
+
pyarrow>=11
|
|
14
18
|
tables>=3.7
|
|
15
19
|
|
|
16
20
|
[test]
|
|
17
|
-
coverage>=
|
|
21
|
+
coverage>=7.2
|
|
18
22
|
dicttoxml>=1.7.16
|
|
19
23
|
matplotlib>=3
|
|
20
24
|
rst2pdf>=0.99
|
|
21
25
|
pytest>=6.2
|
|
22
26
|
pytest-html<4,>=2
|
|
23
27
|
pandas>=1.4
|
|
28
|
+
pyarrow>=11
|
|
24
29
|
tables>=3.7
|
|
@@ -6,7 +6,9 @@ from setuptools import setup
|
|
|
6
6
|
|
|
7
7
|
reqs = [
|
|
8
8
|
"dictdiffer>=0.8",
|
|
9
|
+
"dicttoxml>=1.7.12",
|
|
9
10
|
"diff_pdf_visually>=1.7",
|
|
11
|
+
"jsonpath-ng>=1.5",
|
|
10
12
|
"PyYaml>=6",
|
|
11
13
|
]
|
|
12
14
|
|
|
@@ -18,11 +20,12 @@ doc_reqs = [
|
|
|
18
20
|
|
|
19
21
|
pandas_reqs = [
|
|
20
22
|
"pandas>=1.4",
|
|
23
|
+
"pyarrow>=11",
|
|
21
24
|
"tables>=3.7",
|
|
22
25
|
]
|
|
23
26
|
|
|
24
27
|
test_reqs = [
|
|
25
|
-
"coverage>=
|
|
28
|
+
"coverage>=7.2",
|
|
26
29
|
"dicttoxml>=1.7.16",
|
|
27
30
|
"matplotlib>=3",
|
|
28
31
|
"rst2pdf>=0.99",
|
|
@@ -125,6 +125,12 @@ def dict_diff():
|
|
|
125
125
|
return diff
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
@pytest.fixture
|
|
129
|
+
def base_diff():
|
|
130
|
+
"""The diff that should be reported for the XML files."""
|
|
131
|
+
return r"The files '\S*/file\..{3,4}' and '\S*/file\..{3,4}' are different\."
|
|
132
|
+
|
|
133
|
+
|
|
128
134
|
@pytest.fixture
|
|
129
135
|
def xml_diff(dict_diff):
|
|
130
136
|
"""The diff that should be reported for the XML files."""
|