Skip to content

Commit d4cddf5

Browse files
committed
fix copy_tree to handle warnings and errors properly
1 parent abfba68 commit d4cddf5

File tree

2 files changed

+46
-39
lines changed

2 files changed

+46
-39
lines changed

src/diffpy/cmi/packsmanager.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#
1414
##############################################################################
1515
import shutil
16+
import warnings
1617
from importlib.resources import as_file
1718
from pathlib import Path
1819
from typing import List, Union
@@ -150,7 +151,9 @@ def copy_examples(
150151
Target directory to copy examples into. Defaults to current
151152
working directory.
152153
force : bool, optional
153-
If ``True``, overwrite existing files. Defaults to ``False``.
154+
Defaults to ``False``. If ``True``, existing files are
155+
overwritten and directories are merged
156+
(extra files in the target are preserved).
154157
"""
155158
self._target_dir = target_dir.resolve() if target_dir else Path.cwd()
156159
self._force = force
@@ -207,16 +210,16 @@ def _is_example_name(self, name):
207210
def _copy_tree_to_target(self, pack_name, example_name, src_path):
208211
"""Helper to handle the actual filesystem copy."""
209212
dest_dir = self._target_dir / pack_name / example_name
210-
if dest_dir.exists():
211-
plog.warning(
213+
dest_dir.parent.mkdir(parents=True, exist_ok=True)
214+
if dest_dir.exists() and not self._force:
215+
warn_message = (
212216
f"Example directory(ies): '{dest_dir.stem}' already exist. "
213-
" Current versions of existing files have "
214-
"been left unchanged. To overwrite, please rerun "
215-
"and specify --force."
217+
"Current versions of existing files have been left unchanged. "
218+
"To overwrite, please rerun and specify --force."
216219
)
220+
warnings.warn(warn_message, UserWarning)
217221
return
218-
dest_dir.parent.mkdir(parents=True, exist_ok=True)
219-
shutil.copytree(src_path, dest_dir)
222+
shutil.copytree(src_path, dest_dir, dirs_exist_ok=self._force)
220223

221224
def _resolve_pack_file(self, identifier: Union[str, Path]) -> Path:
222225
"""Resolve a pack identifier to an absolute .txt path.

tests/test_packsmanager.py

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,12 @@ def test_tmp_file_structure(input, expected, example_cases):
105105
# Test various use cases to copy_examples on case5
106106
# 1) copy one example (ambiguous)
107107
# 2) copy list of examples from same pack (ambiguous)
108-
# 3) copy list of examples from different pack (ambiguous)
109-
# 4) copy one example (unambiguous)
110-
# 5) copy list of examples from same pack (unambiguous)
111-
# 6) copy list of examples from different pack (unambiguous)
112-
# 7) copy all examples from a pack
113-
# 8) copy all examples from list of packs
114-
# 9) copy all examples from all packs
108+
# 3) copy one example (unambiguous)
109+
# 4) copy list of examples from same pack (unambiguous)
110+
# 5) copy list of examples from different packs (unambiguous)
111+
# 6) copy all examples from a pack
112+
# 7) copy all examples from list of packs
113+
# 8) copy all examples from all packs
115114
( # 1) copy one example, (ambiguous)
116115
["ex1"],
117116
[
@@ -127,41 +126,34 @@ def test_tmp_file_structure(input, expected, example_cases):
127126
Path("packA/ex2/script3.py"),
128127
],
129128
),
130-
( # 3) copy list of examples from different packs (ambiguous)
131-
["ex1", "ex1"],
132-
[
133-
Path("packA/ex1/path1/script1.py"),
134-
Path("packB/ex1/path2/script2.py"),
135-
],
136-
),
137-
( # 4) copy one example (unambiguous)
129+
( # 3) copy one example (unambiguous)
138130
["ex2"],
139131
[
140132
Path("packA/ex2/script3.py"),
141133
],
142134
),
143-
( # 5) copy list of examples from same pack (unambiguous)
135+
( # 4) copy list of examples from same pack (unambiguous)
144136
["ex3", "ex4"],
145137
[
146138
Path("packB/ex3/script4.py"),
147139
Path("packB/ex4/script5.py"),
148140
],
149141
),
150-
( # 6) copy list of examples from different packs (unambiguous)
142+
( # 5) copy list of examples from different packs (unambiguous)
151143
["ex2", "ex3"],
152144
[
153145
Path("packA/ex2/script3.py"),
154146
Path("packB/ex3/script4.py"),
155147
],
156148
),
157-
( # 7) copy all examples from a pack
149+
( # 6) copy all examples from a pack
158150
["packA"],
159151
[
160152
Path("packA/ex1/path1/script1.py"),
161153
Path("packA/ex2/script3.py"),
162154
],
163155
),
164-
( # 8) copy all examples from list of packs
156+
( # 7) copy all examples from list of packs
165157
["packA", "packB"],
166158
[
167159
Path("packA/ex1/path1/script1.py"),
@@ -171,7 +163,7 @@ def test_tmp_file_structure(input, expected, example_cases):
171163
Path("packB/ex4/script5.py"),
172164
],
173165
),
174-
( # 9) copy all examples from all packs
166+
( # 8) copy all examples from all packs
175167
["all"],
176168
[
177169
Path("packA/ex1/path1/script1.py"),
@@ -255,65 +247,77 @@ def test_copy_examples_location(input, expected_paths, example_cases):
255247
# 3) Path to directory already exists
256248
# 4) No input provided
257249
@pytest.mark.parametrize(
258-
"bad_inputs,expected,path",
250+
"bad_inputs,expected,path,is_warning",
259251
[
260252
(
261253
# 1) Input not found (example or pack).
262254
# Expected: Raise an error with the message.
263255
["bad_example"],
264256
"No examples or packs found for input: 'bad_example'",
265257
None,
258+
False,
266259
),
267260
(
268261
# 2) Mixed good example and bad input.
269262
# Expected: Raise an error with the message.
270263
["ex1", "bad_example"],
271264
"No examples or packs found for input: 'bad_example'",
272265
None,
266+
False,
273267
),
274268
(
275269
# 3) Mixed good pack and bad input.
276270
# Expected: Raise an error with the message.
277271
["packA", "bad_example"],
278272
"No examples or packs found for input: 'bad_example'",
279273
None,
274+
False,
280275
),
281276
(
282277
# 4) Path to directory already exists.
283278
# Expected: Raise a warning with the message.
284279
["ex1"],
285280
(
286281
"Example directory(ies): 'ex1' already exist. "
287-
" Current versions of existing files have "
282+
"Current versions of existing files have "
288283
"been left unchanged. To overwrite, please rerun "
289284
"and specify --force."
290285
),
291286
Path("docs/examples/"),
287+
True,
292288
),
293289
],
294290
)
295-
def test_copy_examples_bad(bad_inputs, expected, path, example_cases):
291+
def test_copy_examples_bad(
292+
bad_inputs, expected, path, is_warning, example_cases
293+
):
296294
examples_dir = example_cases / "case3"
297295
pm = PacksManager(root_path=examples_dir)
298296
target_dir = None if path is None else examples_dir / path
299-
with pytest.raises(Exception, match=re.escape(expected)):
300-
pm.copy_examples(bad_inputs, target_dir=target_dir)
297+
if is_warning:
298+
with pytest.warns(UserWarning, match=re.escape(expected)):
299+
pm.copy_examples(bad_inputs, target_dir=target_dir)
300+
else:
301+
with pytest.raises(FileNotFoundError, match=re.escape(expected)):
302+
pm.copy_examples(bad_inputs, target_dir=target_dir)
301303

302304

303305
def test_copy_examples_force(example_cases):
304306
examples_dir = example_cases / "case3"
305307
pm = PacksManager(root_path=examples_dir)
306-
target_dir = examples_dir / "docs" / "examples"
307-
pm.copy_examples(["packA"], target_dir=target_dir, force=True)
308+
case5dir = example_cases / "case5" / "docs" / "examples"
309+
pm.copy_examples(["packA"], target_dir=case5dir, force=True)
308310
expected_paths = [
311+
Path("packA/ex1/path1/script1.py"),
309312
Path("packA/ex1/script1.py"),
310-
Path("packA/ex2/solution/script2.py"),
313+
Path("packA/ex2/solutions/script2.py"),
314+
Path("packA/ex2/script3.py"),
311315
]
312-
actual = sorted(target_dir.rglob("*.py"))
313-
expected = sorted([target_dir / path for path in expected_paths])
316+
actual = sorted((case5dir / "packA").rglob("*.py"))
317+
expected = sorted([case5dir / path for path in expected_paths])
314318
assert actual == expected
315319
for path in expected_paths:
316-
copied_path = target_dir / path
320+
copied_path = case5dir / path
317321
original_path = examples_dir / path
318322
if copied_path.is_file() and original_path.is_file():
319323
assert copied_path.read_text() == original_path.read_text()

0 commit comments

Comments
 (0)