dir-content-diff 1.12.1__tar.gz → 1.14.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.
Files changed (81) hide show
  1. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/workflows/commitlint.yml +2 -2
  2. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/workflows/publish-sdist.yml +2 -2
  3. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/workflows/run-tox.yml +2 -2
  4. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.gitignore +1 -0
  5. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/CHANGELOG.md +33 -0
  6. {dir_content_diff-1.12.1/dir_content_diff.egg-info → dir_content_diff-1.14.0}/PKG-INFO +115 -6
  7. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/README.md +113 -5
  8. dir_content_diff-1.14.0/dir_content_diff/__init__.py +53 -0
  9. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/base_comparators.py +1 -1
  10. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/cli/__init__.py +34 -2
  11. dir_content_diff-1.14.0/dir_content_diff/config.py +225 -0
  12. dir_content_diff-1.14.0/dir_content_diff/core.py +463 -0
  13. dir_content_diff-1.14.0/dir_content_diff/parallel_utils.py +100 -0
  14. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/pytest_plugin.py +1 -1
  15. dir_content_diff-1.14.0/dir_content_diff/registry.py +114 -0
  16. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/util.py +1 -1
  17. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0/dir_content_diff.egg-info}/PKG-INFO +115 -6
  18. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff.egg-info/SOURCES.txt +6 -0
  19. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff.egg-info/requires.txt +1 -0
  20. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/docs/Makefile +1 -1
  21. dir_content_diff-1.14.0/docs/source/conf.py +209 -0
  22. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/docs/source/index.rst +1 -1
  23. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/pyproject.toml +1 -0
  24. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/comparators/test_pandas.py +17 -16
  25. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/comparators/test_voxcell.py +1 -1
  26. dir_content_diff-1.14.0/tests/test_attrs_validation.py +218 -0
  27. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/test_base.py +209 -75
  28. dir_content_diff-1.14.0/tests/test_parallel_execution.py +371 -0
  29. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/test_util.py +1 -1
  30. dir_content_diff-1.12.1/dir_content_diff/__init__.py +0 -423
  31. dir_content_diff-1.12.1/docs/source/conf.py +0 -107
  32. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.auto-changelog +0 -0
  33. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.auto-changelog-template.hbs +0 -0
  34. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.codespellignorelines +0 -0
  35. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.codespellrc +0 -0
  36. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.copier-answers.yml +0 -0
  37. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.copyright.tmpl +0 -0
  38. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.coveragerc +0 -0
  39. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.coveragerc_min_versions +0 -0
  40. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.gitattributes +0 -0
  41. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  42. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  43. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
  44. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/ISSUE_TEMPLATE/how_to_use.yaml +0 -0
  45. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/dependabot.yml +0 -0
  46. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.github/pull_request_template.md +0 -0
  47. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.gitreview +0 -0
  48. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.pre-commit-config.yaml +0 -0
  49. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.pylintrc +0 -0
  50. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/.readthedocs.yml +0 -0
  51. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/AUTHORS.md +0 -0
  52. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/CONTRIBUTING.md +0 -0
  53. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/LICENSE.txt +0 -0
  54. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/MANIFEST.in +0 -0
  55. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/codecov.yml +0 -0
  56. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/commitlint.config.js +0 -0
  57. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/comparators/__init__.py +0 -0
  58. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/comparators/dependencies.json +0 -0
  59. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/comparators/morphio.py +0 -0
  60. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/comparators/pandas.py +0 -0
  61. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff/comparators/voxcell.py +0 -0
  62. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff.egg-info/dependency_links.txt +0 -0
  63. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff.egg-info/entry_points.txt +0 -0
  64. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/dir_content_diff.egg-info/top_level.txt +0 -0
  65. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/docs/source/.gitignore +0 -0
  66. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/docs/source/api_ref.rst +0 -0
  67. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/docs/source/changelog.rst +0 -0
  68. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/docs/source/contributing.rst +0 -0
  69. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/package.json +0 -0
  70. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/setup.cfg +0 -0
  71. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/setup.py +0 -0
  72. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/__init__.py +0 -0
  73. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/comparators/__init__.py +0 -0
  74. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/comparators/test_morphio.py +0 -0
  75. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/conftest.py +0 -0
  76. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/generate_test_files.py +0 -0
  77. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/test_cli.py +0 -0
  78. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/test_missing_deps.py +0 -0
  79. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/test_pytest_plugin.py +0 -0
  80. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tests/test_version.py +0 -0
  81. {dir_content_diff-1.12.1 → dir_content_diff-1.14.0}/tox.ini +0 -0
@@ -11,8 +11,8 @@ jobs:
11
11
  env:
12
12
  PR_TITLE: ${{ github.event.pull_request.title }}
13
13
  steps:
14
- - uses: actions/checkout@v4
15
- - uses: actions/setup-node@v4
14
+ - uses: actions/checkout@v5
15
+ - uses: actions/setup-node@v5
16
16
  with:
17
17
  node-version: '20'
18
18
  - run: npm install -g --force commitlint @commitlint/cli commitlint-plugin-cleanfeet
@@ -10,9 +10,9 @@ jobs:
10
10
  name: Build and publish on PyPI
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@v4
13
+ - uses: actions/checkout@v5
14
14
  - name: Set up Python 3.10
15
- uses: actions/setup-python@v5
15
+ uses: actions/setup-python@v6
16
16
  with:
17
17
  python-version: "3.10"
18
18
  - name: Build a wheel and a source tarball
@@ -24,9 +24,9 @@ jobs:
24
24
  min_versions: "min_versions"
25
25
 
26
26
  steps:
27
- - uses: actions/checkout@v4
27
+ - uses: actions/checkout@v5
28
28
  - name: Set up Python ${{ matrix.python-version }}
29
- uses: actions/setup-python@v5
29
+ uses: actions/setup-python@v6
30
30
  with:
31
31
  python-version: ${{ matrix.python-version }}
32
32
  - name: Get current month
@@ -30,6 +30,7 @@ test_venv
30
30
  docs/build
31
31
  docs/source/generated
32
32
  docs/source/examples
33
+ docs/source/README_processed.md
33
34
 
34
35
  # reports
35
36
  reports
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.14.0](https://github.com/BlueBrain/dir-content-diff/compare/1.13.0..1.14.0)
4
+
5
+ > 13 September 2025
6
+
7
+ ### New Features
8
+
9
+ - Add parallel iteration over compared files (Adrien Berchet - [#89](https://github.com/BlueBrain/dir-content-diff/pull/89))
10
+
11
+ ### Documentation Changes
12
+
13
+ - Improve README to describe parallel execution (Adrien Berchet - [#91](https://github.com/BlueBrain/dir-content-diff/pull/91))
14
+
15
+ ### Refactoring and Updates
16
+
17
+ - Reorganize the code (Adrien Berchet - [#90](https://github.com/BlueBrain/dir-content-diff/pull/90))
18
+
19
+ ## [1.13.0](https://github.com/BlueBrain/dir-content-diff/compare/1.12.1..1.13.0)
20
+
21
+ > 12 September 2025
22
+
23
+ ### New Features
24
+
25
+ - Add patterns to ignore files in the reference directory (Adrien Berchet - [#86](https://github.com/BlueBrain/dir-content-diff/pull/86))
26
+
27
+ ### Documentation Changes
28
+
29
+ - Fix format in Sphinx (Adrien Berchet - [#85](https://github.com/BlueBrain/dir-content-diff/pull/85))
30
+ - Clarify how reference and compared directories are processed (Adrien Berchet - [#83](https://github.com/BlueBrain/dir-content-diff/pull/83))
31
+
32
+ ### Performance Improvements
33
+
34
+ - Build comparator registry only once before iterating over all files (Adrien Berchet - [#87](https://github.com/BlueBrain/dir-content-diff/pull/87))
35
+
3
36
  ## [1.12.1](https://github.com/BlueBrain/dir-content-diff/compare/1.12.0..1.12.1)
4
37
 
5
38
  > 3 July 2025
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dir-content-diff
3
- Version: 1.12.1
3
+ Version: 1.14.0
4
4
  Summary: Simple tool to compare directory contents.
5
5
  Author: Blue Brain Project, EPFL
6
6
  License: Apache License 2.0
@@ -21,6 +21,7 @@ Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE.txt
23
23
  License-File: AUTHORS.md
24
+ Requires-Dist: attrs>=21.3.0
24
25
  Requires-Dist: click>=8
25
26
  Requires-Dist: dictdiffer>=0.8
26
27
  Requires-Dist: dicttoxml>=1.7.12
@@ -75,7 +76,7 @@ Dynamic: provides-extra
75
76
  This project provides simple tools to compare the content of a directory against a reference
76
77
  directory.
77
78
 
78
- This is useful to check the results of a process that generate several files, like a luigi
79
+ This is useful to check the results of a process that generates several files, like a luigi
79
80
  workflow for example.
80
81
 
81
82
 
@@ -101,7 +102,9 @@ the files, so it is not able to report which values are different.
101
102
 
102
103
  ### Compare two directories
103
104
 
104
- If one wants to compare two directories with the following structures:
105
+ #### Basic Directory Comparison
106
+
107
+ Let's compare two directories with the following structures:
105
108
 
106
109
  ```bash
107
110
  └── reference_dir
@@ -120,6 +123,9 @@ If one wants to compare two directories with the following structures:
120
123
  └── file_1.c
121
124
  ```
122
125
 
126
+ The `reference` directory contains all the files that should be checked in the `compared` directory,
127
+ which means that extraneous files in the `compared` directory are just ignored.
128
+
123
129
  These two directories can be compared with the following code:
124
130
 
125
131
  ```python
@@ -128,7 +134,17 @@ import dir_content_diff
128
134
  dir_content_diff.compare_trees("reference_dir", "compared_dir")
129
135
  ```
130
136
 
131
- This code will return an empty dictionary because no difference was detected.
137
+ > [!WARNING]
138
+ > The order of the parameters is important: the first path is considered as the `reference`
139
+ > directory while the second one is the `compared` directory. Inverting the parameters may return
140
+ > a different result (in this example it would return that the file `sub_file_3.b` is missing).
141
+
142
+ If all the files are identical, this code will return an empty dictionary because no difference
143
+ was detected. As mentioned previously, this is because `dir-content-diff` is only looking for files
144
+ in the compared directory that are also present in the reference directory, so the file
145
+ `sub_file_3.b` is just ignored in this case.
146
+
147
+ #### Using Custom Comparators
132
148
 
133
149
  If ``reference_dir/file_1.c`` is the following JSON-like file:
134
150
 
@@ -171,6 +187,8 @@ The previous code will output the following dictionary:
171
187
  }
172
188
  ```
173
189
 
190
+ #### Assertion-based Comparison
191
+
174
192
  It is also possible to check whether the two directories are equal or not with the following code:
175
193
 
176
194
  ```python
@@ -189,7 +207,9 @@ Changed the value of '[a]' from 1 to 2.
189
207
  Changed the value of '[b][0]' from 1 to 10.
190
208
  ```
191
209
 
192
- Finally, the comparators have parameters that can be passed either to be used for all files of a
210
+ #### Advanced Configuration Options
211
+
212
+ The comparators have parameters that can be passed either to be used for all files of a
193
213
  given extension or only for a specific file:
194
214
 
195
215
  ```python
@@ -217,6 +237,8 @@ dir_content_diff.assert_equal_trees(
217
237
 
218
238
  Each comparator has different arguments that are detailed in the documentation.
219
239
 
240
+ ##### File-specific Comparators
241
+
220
242
  It's also possible to specify a arbitrary comparator for a specific file:
221
243
 
222
244
  ```python
@@ -228,7 +250,9 @@ specific_args = {
228
250
  }
229
251
  ```
230
252
 
231
- And last but not least, it's possible to use regular expressions to associate specific arguments to
253
+ ##### Pattern-based Configuration
254
+
255
+ Another possibility is to use regular expressions to associate specific arguments to
232
256
  a set of files:
233
257
 
234
258
  ```python
@@ -240,6 +264,91 @@ specific_args = {
240
264
  }
241
265
  ```
242
266
 
267
+ ##### File Filtering
268
+
269
+ Last but not least, it's possible to filter files from the reference directory (for example
270
+ because the reference directory contains temporary files that should not be compared). For
271
+ example, the following code will ignore all files whose name does not start with `file_` and does
272
+ not ends with `_tmp.yaml`:
273
+
274
+ ```python
275
+ import dir_content_diff
276
+
277
+ dir_content_diff.compare_trees(
278
+ "reference_dir",
279
+ "compared_dir",
280
+ include_patterns=[r"file_.*"],
281
+ exclude_patterns=[r".*_tmp\.yaml"],
282
+ )
283
+ ```
284
+
285
+
286
+ ### Parallel Execution
287
+
288
+ By default, `dir-content-diff` runs file comparisons sequentially. However, for improved performance when comparing large numbers of files, parallel execution is available using either thread-based or process-based concurrency.
289
+
290
+ #### Configuration Options
291
+
292
+ Parallel execution can be configured using the following parameters:
293
+
294
+ - **`executor_type`**: Controls the type of parallel execution:
295
+ - `"sequential"` (default): No parallel execution, files are compared one by one
296
+ - `"thread"`: Uses `ThreadPoolExecutor` (recommended for I/O-bound tasks)
297
+ - `"process"`: Uses `ProcessPoolExecutor` (recommended for CPU-intensive comparisons)
298
+
299
+ - **`max_workers`**: Maximum number of worker threads/processes. If `None` (default), it defaults to `min(32, (os.cpu_count() or 1) + 4)`.
300
+
301
+ #### Usage Examples
302
+
303
+ Enable thread-based parallel execution:
304
+
305
+ ```python
306
+ import dir_content_diff
307
+
308
+ dir_content_diff.compare_trees(
309
+ "reference_dir",
310
+ "compared_dir",
311
+ executor_type="thread",
312
+ max_workers=8
313
+ )
314
+ ```
315
+
316
+ Enable process-based parallel execution with automatic worker count:
317
+
318
+ ```python
319
+ import dir_content_diff
320
+
321
+ dir_content_diff.compare_trees(
322
+ "reference_dir",
323
+ "compared_dir",
324
+ executor_type="process"
325
+ )
326
+ ```
327
+
328
+ Using a configuration object:
329
+
330
+ ```python
331
+ import dir_content_diff
332
+
333
+ config = dir_content_diff.ComparisonConfig(
334
+ executor_type="thread",
335
+ max_workers=4
336
+ )
337
+
338
+ dir_content_diff.compare_trees(
339
+ "reference_dir",
340
+ "compared_dir",
341
+ config=config
342
+ )
343
+ ```
344
+
345
+ #### Performance Considerations
346
+
347
+ - **Thread-based execution** (`executor_type="thread"`) is generally recommended for most use cases as file comparisons are typically I/O-bound operations
348
+ - **Process-based execution** (`executor_type="process"`) may be beneficial when using computationally intensive comparators or when dealing with very large files
349
+ - Parallel execution is automatically disabled for single file comparisons and falls back to sequential execution when only one file needs to be compared
350
+ - The optimal number of workers depends on your system's capabilities and the nature of your files; too many workers may actually decrease performance due to overhead
351
+
243
352
 
244
353
  ### Export formatted data
245
354
 
@@ -10,7 +10,7 @@
10
10
  This project provides simple tools to compare the content of a directory against a reference
11
11
  directory.
12
12
 
13
- This is useful to check the results of a process that generate several files, like a luigi
13
+ This is useful to check the results of a process that generates several files, like a luigi
14
14
  workflow for example.
15
15
 
16
16
 
@@ -36,7 +36,9 @@ the files, so it is not able to report which values are different.
36
36
 
37
37
  ### Compare two directories
38
38
 
39
- If one wants to compare two directories with the following structures:
39
+ #### Basic Directory Comparison
40
+
41
+ Let's compare two directories with the following structures:
40
42
 
41
43
  ```bash
42
44
  └── reference_dir
@@ -55,6 +57,9 @@ If one wants to compare two directories with the following structures:
55
57
  └── file_1.c
56
58
  ```
57
59
 
60
+ The `reference` directory contains all the files that should be checked in the `compared` directory,
61
+ which means that extraneous files in the `compared` directory are just ignored.
62
+
58
63
  These two directories can be compared with the following code:
59
64
 
60
65
  ```python
@@ -63,7 +68,17 @@ import dir_content_diff
63
68
  dir_content_diff.compare_trees("reference_dir", "compared_dir")
64
69
  ```
65
70
 
66
- This code will return an empty dictionary because no difference was detected.
71
+ > [!WARNING]
72
+ > The order of the parameters is important: the first path is considered as the `reference`
73
+ > directory while the second one is the `compared` directory. Inverting the parameters may return
74
+ > a different result (in this example it would return that the file `sub_file_3.b` is missing).
75
+
76
+ If all the files are identical, this code will return an empty dictionary because no difference
77
+ was detected. As mentioned previously, this is because `dir-content-diff` is only looking for files
78
+ in the compared directory that are also present in the reference directory, so the file
79
+ `sub_file_3.b` is just ignored in this case.
80
+
81
+ #### Using Custom Comparators
67
82
 
68
83
  If ``reference_dir/file_1.c`` is the following JSON-like file:
69
84
 
@@ -106,6 +121,8 @@ The previous code will output the following dictionary:
106
121
  }
107
122
  ```
108
123
 
124
+ #### Assertion-based Comparison
125
+
109
126
  It is also possible to check whether the two directories are equal or not with the following code:
110
127
 
111
128
  ```python
@@ -124,7 +141,9 @@ Changed the value of '[a]' from 1 to 2.
124
141
  Changed the value of '[b][0]' from 1 to 10.
125
142
  ```
126
143
 
127
- Finally, the comparators have parameters that can be passed either to be used for all files of a
144
+ #### Advanced Configuration Options
145
+
146
+ The comparators have parameters that can be passed either to be used for all files of a
128
147
  given extension or only for a specific file:
129
148
 
130
149
  ```python
@@ -152,6 +171,8 @@ dir_content_diff.assert_equal_trees(
152
171
 
153
172
  Each comparator has different arguments that are detailed in the documentation.
154
173
 
174
+ ##### File-specific Comparators
175
+
155
176
  It's also possible to specify a arbitrary comparator for a specific file:
156
177
 
157
178
  ```python
@@ -163,7 +184,9 @@ specific_args = {
163
184
  }
164
185
  ```
165
186
 
166
- And last but not least, it's possible to use regular expressions to associate specific arguments to
187
+ ##### Pattern-based Configuration
188
+
189
+ Another possibility is to use regular expressions to associate specific arguments to
167
190
  a set of files:
168
191
 
169
192
  ```python
@@ -175,6 +198,91 @@ specific_args = {
175
198
  }
176
199
  ```
177
200
 
201
+ ##### File Filtering
202
+
203
+ Last but not least, it's possible to filter files from the reference directory (for example
204
+ because the reference directory contains temporary files that should not be compared). For
205
+ example, the following code will ignore all files whose name does not start with `file_` and does
206
+ not ends with `_tmp.yaml`:
207
+
208
+ ```python
209
+ import dir_content_diff
210
+
211
+ dir_content_diff.compare_trees(
212
+ "reference_dir",
213
+ "compared_dir",
214
+ include_patterns=[r"file_.*"],
215
+ exclude_patterns=[r".*_tmp\.yaml"],
216
+ )
217
+ ```
218
+
219
+
220
+ ### Parallel Execution
221
+
222
+ By default, `dir-content-diff` runs file comparisons sequentially. However, for improved performance when comparing large numbers of files, parallel execution is available using either thread-based or process-based concurrency.
223
+
224
+ #### Configuration Options
225
+
226
+ Parallel execution can be configured using the following parameters:
227
+
228
+ - **`executor_type`**: Controls the type of parallel execution:
229
+ - `"sequential"` (default): No parallel execution, files are compared one by one
230
+ - `"thread"`: Uses `ThreadPoolExecutor` (recommended for I/O-bound tasks)
231
+ - `"process"`: Uses `ProcessPoolExecutor` (recommended for CPU-intensive comparisons)
232
+
233
+ - **`max_workers`**: Maximum number of worker threads/processes. If `None` (default), it defaults to `min(32, (os.cpu_count() or 1) + 4)`.
234
+
235
+ #### Usage Examples
236
+
237
+ Enable thread-based parallel execution:
238
+
239
+ ```python
240
+ import dir_content_diff
241
+
242
+ dir_content_diff.compare_trees(
243
+ "reference_dir",
244
+ "compared_dir",
245
+ executor_type="thread",
246
+ max_workers=8
247
+ )
248
+ ```
249
+
250
+ Enable process-based parallel execution with automatic worker count:
251
+
252
+ ```python
253
+ import dir_content_diff
254
+
255
+ dir_content_diff.compare_trees(
256
+ "reference_dir",
257
+ "compared_dir",
258
+ executor_type="process"
259
+ )
260
+ ```
261
+
262
+ Using a configuration object:
263
+
264
+ ```python
265
+ import dir_content_diff
266
+
267
+ config = dir_content_diff.ComparisonConfig(
268
+ executor_type="thread",
269
+ max_workers=4
270
+ )
271
+
272
+ dir_content_diff.compare_trees(
273
+ "reference_dir",
274
+ "compared_dir",
275
+ config=config
276
+ )
277
+ ```
278
+
279
+ #### Performance Considerations
280
+
281
+ - **Thread-based execution** (`executor_type="thread"`) is generally recommended for most use cases as file comparisons are typically I/O-bound operations
282
+ - **Process-based execution** (`executor_type="process"`) may be beneficial when using computationally intensive comparators or when dealing with very large files
283
+ - Parallel execution is automatically disabled for single file comparisons and falls back to sequential execution when only one file needs to be compared
284
+ - The optimal number of workers depends on your system's capabilities and the nature of your files; too many workers may actually decrease performance due to overhead
285
+
178
286
 
179
287
  ### Export formatted data
180
288
 
@@ -0,0 +1,53 @@
1
+ """dir-content-diff package.
2
+
3
+ Simple tool to compare directory contents.
4
+ """
5
+
6
+ # LICENSE HEADER MANAGED BY add-license-header
7
+ # Copyright (c) 2023-2025 Blue Brain Project, EPFL.
8
+ #
9
+ # This file is part of dir-content-diff.
10
+ # See https://github.com/BlueBrain/dir-content-diff for further info.
11
+ #
12
+ # SPDX-License-Identifier: Apache-2.0
13
+ # LICENSE HEADER MANAGED BY add-license-header
14
+
15
+ import importlib.metadata
16
+
17
+ # Import base comparators
18
+ from dir_content_diff.base_comparators import BaseComparator
19
+ from dir_content_diff.base_comparators import DefaultComparator
20
+
21
+ # Import core comparison functionality
22
+ from dir_content_diff.config import ComparisonConfig
23
+ from dir_content_diff.core import ComparatorType
24
+ from dir_content_diff.core import assert_equal_trees
25
+ from dir_content_diff.core import compare_files
26
+ from dir_content_diff.core import compare_trees
27
+ from dir_content_diff.core import export_formatted_file
28
+ from dir_content_diff.core import pick_comparator
29
+
30
+ # Import comparator registry functionality
31
+ from dir_content_diff.registry import get_comparators
32
+ from dir_content_diff.registry import register_comparator
33
+ from dir_content_diff.registry import reset_comparators
34
+ from dir_content_diff.registry import unregister_comparator
35
+
36
+ __version__ = importlib.metadata.version("dir-content-diff")
37
+
38
+ # For backward compatibility, ensure all public functions are available
39
+ __all__ = [
40
+ "ComparisonConfig",
41
+ "ComparatorType",
42
+ "assert_equal_trees",
43
+ "compare_files",
44
+ "compare_trees",
45
+ "export_formatted_file",
46
+ "pick_comparator",
47
+ "get_comparators",
48
+ "register_comparator",
49
+ "reset_comparators",
50
+ "unregister_comparator",
51
+ "BaseComparator",
52
+ "DefaultComparator",
53
+ ]
@@ -513,7 +513,7 @@ class XmlComparator(DictComparator):
513
513
  elif str(text).lower() == "false":
514
514
  res = False
515
515
  else:
516
- raise ValueError("Bool attributes expect 'true' or 'false'.")
516
+ raise ValueError("Boolean attributes expect 'true' or 'false'.")
517
517
  elif value_type == "list":
518
518
  res = []
519
519
  elif value_type == "dict":
@@ -13,15 +13,17 @@ import json
13
13
  import logging
14
14
  import sys
15
15
  from pathlib import Path
16
+ from typing import Optional
17
+ from typing import Union
16
18
 
17
19
  import click
18
20
  from yaml import safe_load
19
21
 
20
- from dir_content_diff import _DEFAULT_EXPORT_SUFFIX
21
22
  from dir_content_diff import compare_files
22
23
  from dir_content_diff import compare_trees
23
24
  from dir_content_diff import export_formatted_file
24
25
  from dir_content_diff import pick_comparator
26
+ from dir_content_diff.core import _DEFAULT_EXPORT_SUFFIX
25
27
  from dir_content_diff.util import LOGGER
26
28
 
27
29
 
@@ -102,6 +104,19 @@ def load_config(ctx, param, value): # pylint: disable=unused-argument
102
104
  default=False,
103
105
  help="Sort the differences by file name.",
104
106
  )
107
+ @click.option(
108
+ "--executor-type",
109
+ type=click.Choice(["sequential", "thread", "process"]),
110
+ default="sequential",
111
+ help="Type of executor for execution. 'sequential' for single-threaded, "
112
+ "'thread' for I/O-bound tasks, 'process' for CPU-bound tasks.",
113
+ )
114
+ @click.option(
115
+ "--max-workers",
116
+ type=int,
117
+ help="Maximum number of worker threads/processes for parallel execution. "
118
+ "Only used with 'thread' or 'process' executor types.",
119
+ )
105
120
  @click.version_option()
106
121
  @click.pass_context
107
122
  def main(ctx, *args, **kwargs):
@@ -120,16 +135,31 @@ def main(ctx, *args, **kwargs):
120
135
 
121
136
  ref = Path(kwargs.pop("reference_input"))
122
137
  comp = Path(kwargs.pop("compared_input"))
138
+
139
+ # Extract execution options
140
+ executor_type = kwargs.pop("executor_type", "sequential")
141
+ max_workers = kwargs.pop("max_workers", None)
142
+
123
143
  input_diff(
124
144
  ref,
125
145
  comp,
126
146
  ctx.config,
127
147
  kwargs.pop("export_formatted_files", False),
128
148
  kwargs.pop("sort_diffs", False),
149
+ executor_type,
150
+ max_workers,
129
151
  )
130
152
 
131
153
 
132
- def input_diff(ref, comp, config, export_formatted_files=False, sort_diffs=False):
154
+ def input_diff(
155
+ ref: Union[str, Path],
156
+ comp: Union[str, Path],
157
+ config,
158
+ export_formatted_files: Union[bool, str] = False,
159
+ sort_diffs: bool = False,
160
+ executor_type: str = "sequential",
161
+ max_workers: Optional[int] = None,
162
+ ):
133
163
  """Compute and display differences from given inputs."""
134
164
  ref = Path(ref)
135
165
  comp = Path(comp)
@@ -145,6 +175,8 @@ def input_diff(ref, comp, config, export_formatted_files=False, sort_diffs=False
145
175
  comp,
146
176
  specific_args=config,
147
177
  export_formatted_files=export_formatted_files,
178
+ executor_type=executor_type,
179
+ max_workers=max_workers,
148
180
  )
149
181
  else:
150
182
  comparator_name = config.pop("comparator", None)