Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@tsvikas
Copy link
Contributor

@tsvikas tsvikas commented Feb 27, 2025

Introduce the TaskError exception to handle subprocess failures gracefully.

solves #1990

Copy link
Member

@sisp sisp left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution, @tsvikas! 🙏

I have two minor suggestions (see inline comment).

IIUC, a failing task will raise the TaskError exception and the message will be shown, but the error from the task command will be hidden when running Copier via CLI, as it's not printed anymore but only accessible via the stdout/stderr attributes of the exception object. I think we should catch this exception in

copier/copier/cli.py

Lines 66 to 80 in a731d2b

def _handle_exceptions(method: Callable[[], None]) -> int:
"""Handle keyboard interruption while running a method."""
try:
try:
method()
except KeyboardInterrupt:
raise UserMessageError("Execution stopped by user")
except UserMessageError as error:
print(colors.red | "\n".join(error.args), file=sys.stderr)
return 1
except UnsafeTemplateError as error:
print(colors.red | "\n".join(error.args), file=sys.stderr)
# DOCS https://github.com/copier-org/copier/issues/1328#issuecomment-1723214165
return 0b100
return 0

and print the message plus stdout/stderr.

@tsvikas
Copy link
Contributor Author

tsvikas commented Feb 27, 2025

IIUC, a failing task will raise the TaskError exception and the message will be shown, but the error from the task command will be hidden when running Copier via CLI, as it's not printed anymore but only accessible via the stdout/stderr attributes of the exception object. I think we should catch this exception in

Actually, the stdout/stderr will be None, since we did not capture them in subprocess.run(), and the output of the process still goes to the screen.

So we only need to output the message itself ("Task {command!r} returned non-zero exit status {returncode}.").
I believe that inheriting from UserMessageError will take care of that nicely?

@tsvikas
Copy link
Contributor Author

tsvikas commented Feb 27, 2025

I did all of the requested changes, except inheriting from CalledProcessError (see my comment above) - waiting for more input for that.
Hopefully i didn't do too much mess in the comments/review here 🙏

@tsvikas tsvikas requested a review from sisp March 2, 2025 14:43
@tsvikas
Copy link
Contributor Author

tsvikas commented Mar 2, 2025

Done :)

Copy link
Member

@sisp sisp left a comment

Choose a reason for hiding this comment

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

@tsvikas I have a few minor remarks.

@copier-org/maintainers What is the correct use of UserMessageError? When I check its occurrences, it seems this error is directly raised when it originates from an improper use of Copier that can be fixed by the user. One counter-example I've seen is a missing --unsafe/--trust flag which raises UnsafeTemplateError (which isn't a subclass of UserMessageError), although this problem can be resolved by a Copier user by adding the flag. What about a task error? Is it an error caused mainly by a template author which can't be fixed by a user? Is subclassing UserMessageError appropriate in this case? WDYT?

@tsvikas
Copy link
Contributor Author

tsvikas commented Mar 2, 2025

@copier-org/maintainers What is the correct use of UserMessageError? When I check its occurrences, it seems this error is directly raised when it originates from an improper use of Copier that can be fixed by the user. One counter-example I've seen is a missing --unsafe/--trust flag which raises UnsafeTemplateError (which isn't a subclass of UserMessageError), although this problem can be resolved by a Copier user by adding the flag. What about a task error? Is it an error caused mainly by a template author which can't be fixed by a user? Is subclassing UserMessageError appropriate in this case? WDYT?

personally i assumed that UserMessageError is for any exception that should be reported to the user in a message.
if it is not the case, i will be happy for input about the way the @copier-org/maintainers prefer to use to report something to the user. is it the printf_exception function? something else? i don't want to add here a 2nd/3rd way to do something simple.

@tsvikas tsvikas requested a review from sisp March 3, 2025 13:26
@tsvikas
Copy link
Contributor Author

tsvikas commented Mar 20, 2025

Hey @sisp , I saw you pinged the maintainers earlier regarding the UserMessageError—do you think it would be helpful to re-ping them, or should we proceed with an alternative approach?
Just want to make sure we align with the project’s conventions before finalizing this. Thanks!

@pawamoy
Copy link
Contributor

pawamoy commented Mar 20, 2025

What is the correct use of UserMessageError?

I would just be guessing so I'll leave that to @yajo 🙇

@sisp
Copy link
Member

sisp commented Mar 21, 2025

I'd like to wait for @yajo's opinion, as he is the lead maintainer with a long history developing Copier. All of us are working on Copier as time permits. Let's just wait for him to find time to share his thoughts.

tsvikas added 7 commits March 22, 2025 17:59
Introduce the TaskError exception to handle subprocess failures gracefully.
Modify `Worker` class in `copier/main.py` to use TaskError when a subprocess encounters a non-zero exit status.
Update tests to expect TaskError instead of CalledProcessError.
Checked for all uses of UserMessageError to verify that it'll work.
Also added __str__ method to UserMessageError.
@codecov
Copy link

codecov bot commented Mar 22, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.00%. Comparing base (82dd2cb) to head (8269c7b).
Report is 140 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1991      +/-   ##
==========================================
- Coverage   98.13%   98.00%   -0.13%     
==========================================
  Files          54       54              
  Lines        5832     5915      +83     
==========================================
+ Hits         5723     5797      +74     
- Misses        109      118       +9     
Flag Coverage Δ
unittests 98.00% <100.00%> (-0.13%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@yajo
Copy link
Member

yajo commented Mar 23, 2025

IIRC, UserMessageError is an error that should send a message to the user, just like the name says. It should not contain a traceback, because that would be confusing.

@sisp
Copy link
Member

sisp commented Mar 23, 2025

Just to clarify your comment, @yajo: Is it a valid pattern to let any exception (like TaskError) inherit from UserMessageError that shall print an error message to the user (i.e., errors that can be addressed by a template user, not author)? If so, should UnsafeTemplateError also inherit from UserMessageError? If yes, then we'd need to catch specific subclasses of UserMessageError such as UnsafeTemplateError in the CLI's exception handler first before catching UserMessageError to support exit codes other than 1 for some errors (like UnsafeTemplateError for which the CLI exits with code 4).

I guess the underlying question is: What is the meaning of UserMessageError and when to subclass it vs. just subclassing CopierError or other child classes? My confusion originates from an attempt to decouple API and CLI, such that (IMHO) exceptions shouldn't be designed with CLI-related error management in mind; CLI error handling should rather be an afterthought, as a CLI is merely one possible alternative interface to the Python API with its own peculiarities of managing errors.

@sisp
Copy link
Member

sisp commented Apr 18, 2025

@tsvikas Sorry for the delay. I've thought about the use of UserMessageError some more and believe the way you're using it here makes sense. 👍 Just one more thought: The new error handling for CLI users swallows all error information from the task process, which may make it difficult to understand the reason for the error. How about printing also stderr in addition to the generic message?

@tsvikas
Copy link
Contributor Author

tsvikas commented Apr 20, 2025

Thank you

I've implemented printing of stderr in TaskError. As a reminder, this won’t affect the current case, since the task execution runs without capturing, so both stdout and stderr are None (and were already printed to the screen). But it could be useful for future usages of TaskError.

@tsvikas
Copy link
Contributor Author

tsvikas commented Apr 20, 2025

it seems that the codecov is complaining about those lines being untested, which is true, because any task used currently will not capture the stderr -- this is only a future facing change.
I think that in order to test it we will need to write code that uses TaskError in a different way than in the codebase itself, which seems too artificial for a test.
Alternatively we can add ignore for codecov, or revert this change, or revert this change and assert stderr is None to prevent wrong usage of TaskError.

what do you think we should do?

@sisp
Copy link
Member

sisp commented Apr 20, 2025

Ah, I missed that stderr is always None and both stdout and stderr are printed unconditionally at the moment. Thanks for the reminder! Eventually, I think we should handle task error printing like pre-commit:

  • When no error occurs, nothing is printed by default. Printing captured stdout/stderr is opt-in via the --verbose switch. In Copier, --quiet and a new --verbose switch would be mutually exclusive.
  • When an error occurs, captured stdout/stderr is printed by default. In Copier, we would suppress it when our --quiet switch is used.

For our Python API users, TaskError's stdout/stderr attributes would contain the captured output of a failed task for further processing.

That said, I suggest to revert/drop db1a6dc (and f29ce78) to keep this PR focused on introducing the TaskError exception and address further enhancements in subsequent PRs. Sorry for the confusion.

@tsvikas
Copy link
Contributor Author

tsvikas commented Apr 21, 2025

done :)

Copy link
Member

@sisp sisp left a comment

Choose a reason for hiding this comment

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

Fantastic, thanks for the great work, @tsvikas! 🙏

@sisp sisp merged commit a812e14 into copier-org:master Apr 21, 2025
21 of 22 checks passed
@tsvikas tsvikas deleted the feat/task-error branch April 22, 2025 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants