Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-73991: Add pathlib.Path.copytree() #120718

Merged
merged 4 commits into from
Jun 23, 2024
Merged

Conversation

barneygale
Copy link
Contributor

@barneygale barneygale 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
Contributor Author

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.

@barneygale barneygale requested a review from pfmoore June 19, 2024 02:31
@barneygale
Copy link
Contributor Author

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

Doc/library/pathlib.rst Outdated Show resolved Hide resolved
Lib/test/test_pathlib/test_pathlib.py Outdated Show resolved Hide resolved
Lib/test/test_pathlib/test_pathlib_abc.py Outdated Show resolved Hide resolved
Copy link
Member

@pfmoore pfmoore 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.

Lib/test/test_pathlib/test_pathlib_abc.py Outdated Show resolved Hide resolved
Lib/test/test_pathlib/test_pathlib_abc.py Outdated Show resolved Hide resolved
Lib/test/test_pathlib/test_pathlib_abc.py Show resolved Hide resolved
@barneygale
Copy link
Contributor Author

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

pfmoore commented Jun 23, 2024

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.

@barneygale barneygale merged commit 35e998f into python:main Jun 23, 2024
35 checks passed
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
Sign up for free to 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