-
Notifications
You must be signed in to change notification settings - Fork 6
Replace RuntimeErrors with custom exceptions #486
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
base: main
Are you sure you want to change the base?
Conversation
Swap all RuntimeError exceptions for custom fab exceptions using a class hierarchy derived from RuntimeError to minimise distruptive changes.
Some minor flake8 problems had slipped in. This fixes them.
hiker
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this huge amount of work. I didn't expect that many classes to be used :)
FWIW, in psyclone we have around half the errors defined in some submodules (which helps avoid circular dependencies to some degree).
I have added a few comments. Could you also add a section to the development.rst file about errors, e.g. indicating that only Fab* errors should be raised, no standard errors.
source/fab/errors.py
Outdated
| self.tool = tool | ||
|
|
||
| # Check for name attributes rather than using isintance | ||
| # because Category and Tool ahve issues with circular |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think Category has a problem by itself, so I think that can be moved out of the TYPE_CHECKING test above (and fix the type here from string to be Category) - at least it worked for me).
Then I find the name tool confusing, since it can also be a category? Would it be worth storing both (tool and category, and leaving one of them None?? That might be useful for messages that a tool has the wrong category somewhere??). Or just call it tool_or_category?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have thoughts on replacing the concept of categories with a more flexible system of capability tags. How does that interact with this issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my previous comment was a note to myself. It is an not an issue for this change.
source/fab/errors.py
Outdated
| if hasattr(tool, "name"): | ||
| self.name = tool.name | ||
| elif hasattr(tool, "_name"): | ||
| self.name = tool._name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what class this is, but please add a setter, don't access what is intended to be a private attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A getter in this case? Agree that we shouldn't be accessing nominally private state.
| """ | ||
|
|
||
| def __init__(self, tool: Union["Category", "Tool", str], message: str) -> None: | ||
| self.tool = tool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm - a coding style question: I prefer all attributes of a class to be 'private' (i.e. starting with _, and then add getter and/or setter as required). The big advantage (imho) of this is that the setter/getter will be documented, so a user can check out the supported methods in the documentation.
We recently investigated the performance impact of a getter (see stfc/PSyclone#3082), and found a 3 to 4% improvement when avoiding the getter. That's for getters that are being called 100.000s of time, I don't think we will notice a performance disadvantage of this in Fab.
I am ok leaving it as is (esp. since afaik it's not a pep8 requirement), but would prefer a clarification in the coding style :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I too prefer private variables and getters/setters. It allows for validation on change of implementation without altering the API.
The @property modifier is available if the () of a function offends.
source/fab/errors.py
Outdated
| preserved for inspection by the caller. | ||
| """ | ||
|
|
||
| def __init__(self, command: str) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typing error: Union[list[str].str]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See Matthew's comment: we know that the command being passed to the constructor has a type of List[str], so we can change the type hinting and always execute the " ".join(command) line.
source/fab/errors.py
Outdated
| else: | ||
| raise ValueError(f"invalid command: {command}") | ||
|
|
||
| super().__init__(f"unable to execute {self.target}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer quotes like '{self.target}' around the string. This helps identify white-space errors (e.g. when a user provides say touch abc as command, since we are running without shell, that line would result in unable to execute touch abc, which kind of hides the error that there is a space in the binary name.
| """ | ||
|
|
||
| def __init__(self, tool: str, reason: str, revision: Optional[str] = None) -> None: | ||
| self.tool = tool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confused at first, but then realised that this is not based on FabToolError. Nothing we can do about it, but maybe add a comment to explain this?
|
I'm about to start a review but I'll leave this comment here for immediate consideration. Are you aware of |
MatthewHambley
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an extensive change which tidies up exception handling considerable. A few questions and suggestions included in-line.
In general, we have generated API documentation but is there anything which could be added (or removed) from the static documentation in light of this change?
| """ | ||
|
|
||
| def __init__(self, tool: Union["Category", "Tool", str], message: str) -> None: | ||
| self.tool = tool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I too prefer private variables and getters/setters. It allows for validation on change of implementation without altering the API.
The @property modifier is available if the () of a function offends.
source/fab/errors.py
Outdated
| self.tool = tool | ||
|
|
||
| # Check for name attributes rather than using isintance | ||
| # because Category and Tool ahve issues with circular |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have thoughts on replacing the concept of categories with a more flexible system of capability tags. How does that interact with this issue?
source/fab/errors.py
Outdated
| if hasattr(tool, "name"): | ||
| self.name = tool.name | ||
| elif hasattr(tool, "_name"): | ||
| self.name = tool._name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A getter in this case? Agree that we shouldn't be accessing nominally private state.
tests/unit_tests/test_errors.py
Outdated
| # Mock defines an internal name property, so special handling | ||
| # is required to reset the value to support testing | ||
| tool = Mock(_name="tool cc") | ||
| del tool.name | ||
| err = FabToolError(tool, "compiler message") | ||
| assert str(err) == "[tool cc] compiler message" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems alarming. Is this related to Joerg's comment about peeking at private state?
|
@yaswant , I am a bit confused: I already did a review, and as far as I can see no work was done on the PR (and neither was there a reply to my comments). Additionally, the branch has conflicts ;) |
|
@hiker please ignore my assignment. You are right, Sam has not responded to your comments yet. Apologies for the confusion. |
Replace the use of built-in python exceptions with Fab exception classes and AssertionErrors.
There appear to be two problems with the github continuous integration tests. The format of the argparse help for choices is quoted in some versions of python, so omit the choice strings from the output check. The git system tests need details of the current user to run. Add a step which adds some temporary user details.
t00sa
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearly, some things I still need to tidy up. I'll get on with them tomorrow and update the PR when they're ready.
| git config --global user.email "fab" | ||
| git config --global user.name "Fab Testing" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you need this for git merge. There is no potential issue here, but does this have to be --global config? I know there is no strict validation here, but wondering if [email protected] as user.email would be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It probably doesn't have to be, but this is what git suggests when the configuration items don't exist
|
I have addressed the comments from the previous reviews, the CI tests pass, and I've successfully used my branch to build skeleton in LFRic core using Jason's branch, so I think this ready for another round of reviewing. |
MatthewHambley
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I think there's lots more work to do in the general area of error and exception handling, this is a good change and I'd rather see it on sooner and continue development than wait for it to be perfect.
A few comments left over from the previous review which haven't been addressed either with a change or a counter-comment.
In case you didn't know, if you from __future__ import annotate you don't need to quote type hints which have not yet been (or are currently being) defined.
| if not isinstance(ar, Ar): | ||
| raise FabToolMismatch(ar.name, type(ar), "Ar") | ||
| output_fpath = str(output_fpath) if output_fpath else None | ||
| # output_fpath = str(output_fpath) if output_fpath else None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented-out code - This looks like development detritus.
source/fab/errors.py
Outdated
| self.tool = tool | ||
|
|
||
| # Check for name attributes rather than using isintance | ||
| # because Category and Tool ahve issues with circular |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my previous comment was a note to myself. It is an not an issue for this change.
Swap all RuntimeError exceptions for custom fab exceptions using a class hierarchy derived from RuntimeError to minimise disruptive changes.
These changes fall into three broad areas:
fab.errorsand an associated set of testsRuntimeErrorto test the impact on calling functions - match the expected instance of the new error classraise RuntimeErroracross the code base with the new error classesWhile the change is complete and the test suite runs successfully in my environment, I still have a couple of outstanding questions that need to be addressed in review:
Errorbut the derived classes do not. Ending every class name withErrorwould make fab more consistent with python, at the cost of longer exception names