gh-102931: Use explicit exception when shutil._copytree should group exceptions#128309
gh-102931: Use explicit exception when shutil._copytree should group exceptions#128309berland wants to merge 2 commits intopython:mainfrom
Conversation
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
|
This is based on @eric-wieser's work in #102827 but with tests adde and a different approach to resolution. |
| copy_function(srcobj, dstname) | ||
| # catch the Error from the recursive copytree so that we can | ||
| # continue with other files | ||
| except SameFileError as err: |
There was a problem hiding this comment.
I think you need the same handler for SpecialFileError, which is also instantiated with a string.
There was a problem hiding this comment.
(and possibly the other errors too, I haven't attempted to trace them all).
Using ExceptionGroup here might make things cleaner, but is probably backwards-incompatible.
There was a problem hiding this comment.
I will try to reproduce that with a test.
There was a problem hiding this comment.
I could not make a scenario that would yield this error (a splitted string in the error message) when a SpecialFileError is triggered. There is already a test function that checks the error string when a SpecialFileError is provoked with a pipe
cpython/Lib/test/test_shutil.py
Line 999 in ffece55
which already passes and that behaviour is not changed in this PR. In that case there is recursion involved, and then the existing code works. The bug with splitted strings only occurs when there is no recursion.
ZeroIntensity
left a comment
There was a problem hiding this comment.
Some high-level comments.
b8dc15b to
470b7bd
Compare
| except SameFileError as err: | ||
| errors.append((srcname, dstname, str(err))) | ||
| except Error as err: | ||
| errors.extend(err.args[0]) |
There was a problem hiding this comment.
Would it instead be better to use:
if type(e) is Error:
errors.extend(err.args[0])
else:
errors.append((srcname, dstname, str(err)))which perhaps is more robust
There was a problem hiding this comment.
Yes, it is more robust if there are more bugs that we have not found. It is best if we could find them too and add a test case for them.
If it is impossible to trigger other exceptions in the outermost recursion layer, I would prefer the code to be explicit and only catch what is attainable.
There was a problem hiding this comment.
I would prefer the code to be explicit and only catch what is attainable.
Agreed; perhaps then we should add
class ErrorGroup(Error): passand throw/catch this when grouping the exceptions. That way it is explicit that this merging behavior is actually going to do the right thinking, rather than hoping the exception is the one we think it is.
There was a problem hiding this comment.
Thanks, that looked good in the code! Fixed
6e9603b to
101bf86
Compare
| except Error as err: | ||
| errors.extend(err.args[0]) | ||
| errors.append((srcname, dstname, str(err))) |
There was a problem hiding this comment.
Error extends OSError which is already caught below, so there is no need to catch this any more.
eric-wieser
left a comment
There was a problem hiding this comment.
Thanks! It would be cool to use ExceptionGroup here, but that would probably be backwards incompatible so probably isn't possible.
baff88d to
533be29
Compare
|
@eric-wieser is there anything I can do to make this enter "core review"? |
| try: | ||
| shutil.copytree(src_dir, target_dir, dirs_exist_ok=True) | ||
| self.fail("shutil.Error should have been raised") | ||
| except Error as error: |
There was a problem hiding this comment.
Use self.assertRaisesRegex instead of try-except please or work with the context manager.
There was a problem hiding this comment.
I don't think self.assertRaisesRegex is appropriate here, since we're checking a tuple-valued args.
There was a problem hiding this comment.
Oh right. Then just assertRaises
| pass | ||
|
|
||
| class ErrorGroup(Error): | ||
| """Raised when multiple exceptions have been caught""" |
There was a problem hiding this comment.
| """Raised when multiple exceptions have been caught""" | |
| """Raised when multiple exceptions have been caught.""" |
There was a problem hiding this comment.
Thanks for your review @picnixz , this should now be fixed. Will squash the commits later, but kept the review-fix as separate commit for now.
There was a problem hiding this comment.
Don't bother squashing them. We do it ourselves at the end. Squashing commits is not always good since we do incremental reviews. Only force-pushes when (1) there is no review yet (2) there was a real issue with the history
This resolves a bug where a non-recursive error is assumed to be a
list of exceptions and presenting the error as:
```
shutil.Error: ['<', 'D', 'i', 'r', 'E', 'n', 't', 'r', 'y', ' ', ...,
's', 'a', 'm', 'e', ' ', 'f', 'i', 'l', 'e']
```
3d03a5f to
da49c09
Compare
|
This PR is stale because it has been open for 30 days with no activity. |
If not, it is caught as a shutil.Error for which it is assumed to be a recursive error, resulting in splitting of the error string as