Skip to content

Conversation

@barneygale
Copy link
Contributor

@barneygalebarneygale commented Jun 19, 2024

Add pathlib.Path.copytree() method, which recursively copies one directory to another.

This differs from shutil.copytree() in the following respects:

  1. Our method has a follow_symlinks argument, whereas shutil's has a symlinks argument with an inverted meaning.
  2. Our method lacks something like a copy_function argument. It always uses Path.copy() to copy files.
  3. Our method lacks something like a ignore_dangling_symlinks argument. Instead, users can filter out danging symlinks with ignore, or ignore exceptions with on_error
  4. Our ignore argument is a callable that accepts a single path object, whereas shutil's accepts a path and a list of child filenames.
  5. We add an on_error argument, which is a callable that accepts an OSError instance. (Path.walk() also accepts such a callable). This is done instead of aggregating exceptions into an shutil.Error instance.

📚 Documentation preview 📚: https://cpython-previews--120718.org.readthedocs.build/

Add `pathlib.Path.copytree()` method, which recursively copies one directory to another. This differs from `shutil.copytree()` in the following respects: 1. Our method has a *follow_symlinks* argument, whereas shutil's has a *symlinks* argument with an inverted meaning. 2. Our method lacks something like a *copy_function* argument. It always uses `Path.copy()` to copy files. 3. Our method lacks something like a *ignore_dangling_symlinks* argument. Instead, users can filter out danging symlinks with *ignore*, or ignore exceptions with *on_error* 4. Our *ignore* argument is a callable that accepts a single path object, whereas shutil's accepts a path and a list of child filenames. 5. We add an *on_error* argument, which is a callable that accepts an `OSError` instance. (`Path.walk()` also accepts such a callable).
@barneygale
Copy link
ContributorAuthor

Some parts of the design aren't obvious, particularly the ignore and on_error arguments. Happy to adjust or even remove some arguments if we'd like to kick the can down the road - I don't think either of those arguments are essential to expose straight away.

@barneygalebarneygale requested a review from pfmooreJune 19, 2024 02:31
@barneygale
Copy link
ContributorAuthor

@pfmoore I've requested your review as you've already given this idea some scrutiny on the forum! Hope that's alright, thank you.

Copy link
Member

@pfmoorepfmoore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code LGTM. I've dome a partial review of the tests, but I'll be honest I'm not entirely clear what the follow_symlinks argument does (it's not something I use myself) so I didn't really review those parts of the tests.

I'll try to find some time to do a more detailed review later.

Co-authored-by: Nice Zombies <nineteendo19d0@gmail.com>
barneygaleand others added 2 commits June 23, 2024 17:37
Co-authored-by: Nice Zombies <nineteendo19d0@gmail.com>
@barneygale
Copy link
ContributorAuthor

barneygale commented Jun 23, 2024

Thanks for the reviews and fixes, both.

I'm not entirely clear what the follow_symlinks argument does (it's not something I use myself) so I didn't really review those parts of the tests.

The default behaviour (follow_symlinks=True) dereferences any symlinks in the source tree, so the final destination tree never contains symlinks. If you set follow_symlinks=False, any symlinks in the source tree will be re-created in the destination tree with the same targets.

Our forthcoming implementation of Path.move() will call source.copytree(target, follow_symlinks=False) then source.rmtree() (roughly), because users expect "move" operations to preserve symlinks (not follow them).

@pfmoore
Copy link
Member

Thanks for the explanation - that's the clearest I've seen it expressed, and for the first time (possibly ever) I get what's going on.

@barneygalebarneygale merged commit 35e998f into python:mainJun 23, 2024
mrahtz pushed a commit to mrahtz/cpython that referenced this pull request Jun 30, 2024
Add `pathlib.Path.copytree()` method, which recursively copies one directory to another. This differs from `shutil.copytree()` in the following respects: 1. Our method has a *follow_symlinks* argument, whereas shutil's has a *symlinks* argument with an inverted meaning. 2. Our method lacks something like a *copy_function* argument. It always uses `Path.copy()` to copy files. 3. Our method lacks something like a *ignore_dangling_symlinks* argument. Instead, users can filter out danging symlinks with *ignore*, or ignore exceptions with *on_error* 4. Our *ignore* argument is a callable that accepts a single path object, whereas shutil's accepts a path and a list of child filenames. 5. We add an *on_error* argument, which is a callable that accepts an `OSError` instance. (`Path.walk()` also accepts such a callable). Co-authored-by: Nice Zombies <nineteendo19d0@gmail.com>
noahbkim pushed a commit to hudson-trading/cpython that referenced this pull request Jul 11, 2024
Add `pathlib.Path.copytree()` method, which recursively copies one directory to another. This differs from `shutil.copytree()` in the following respects: 1. Our method has a *follow_symlinks* argument, whereas shutil's has a *symlinks* argument with an inverted meaning. 2. Our method lacks something like a *copy_function* argument. It always uses `Path.copy()` to copy files. 3. Our method lacks something like a *ignore_dangling_symlinks* argument. Instead, users can filter out danging symlinks with *ignore*, or ignore exceptions with *on_error* 4. Our *ignore* argument is a callable that accepts a single path object, whereas shutil's accepts a path and a list of child filenames. 5. We add an *on_error* argument, which is a callable that accepts an `OSError` instance. (`Path.walk()` also accepts such a callable). Co-authored-by: Nice Zombies <nineteendo19d0@gmail.com>
estyxx pushed a commit to estyxx/cpython that referenced this pull request Jul 17, 2024
Add `pathlib.Path.copytree()` method, which recursively copies one directory to another. This differs from `shutil.copytree()` in the following respects: 1. Our method has a *follow_symlinks* argument, whereas shutil's has a *symlinks* argument with an inverted meaning. 2. Our method lacks something like a *copy_function* argument. It always uses `Path.copy()` to copy files. 3. Our method lacks something like a *ignore_dangling_symlinks* argument. Instead, users can filter out danging symlinks with *ignore*, or ignore exceptions with *on_error* 4. Our *ignore* argument is a callable that accepts a single path object, whereas shutil's accepts a path and a list of child filenames. 5. We add an *on_error* argument, which is a callable that accepts an `OSError` instance. (`Path.walk()` also accepts such a callable). Co-authored-by: Nice Zombies <nineteendo19d0@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@barneygale@pfmoore@nineteendo