-
Notifications
You must be signed in to change notification settings - Fork 192
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
WorkChain
: Protect public methods from being subclassed
#5779
WorkChain
: Protect public methods from being subclassed
#5779
Conversation
@danielhollas I think this fix should have saved us some debugging time in #5775 😅 |
@sphuber did you mean to tag @janssenhenning ? In any case, this is cool Python magic. 👍 I would additionally propose to rename the run method to something less generic so that the potential clash is minimized. Not sure if that has any potential to break things though. |
@sphuber thanks! Before going into details, there are some github hints in the review panel, like below. What are they, is that indicate some glitches need to be taken care of? |
Edit, now I understand, the run method is part of public API, so cannot be changed. Sorry for the confusion. |
I did indeed 😅 apologies.
Not a bad suggestion, but this would require changes in |
These have been there for a long long time. I am not sure how they got enabled, but I have the feeling this is GHA trying to be clever. It seems to be picking up on exceptions that get raised in our tests, but these tests are doing that on purpose and so that is exactly what we expect. |
e396edf
to
902a95b
Compare
The paradigm of the `WorkChain` requires a user in an implementation to define the workflow logic through classmethods of the `WorkChain` subclass. While this gives great flexibility and choice to the user, there is a risk that a user inadvertently chooses a method name that already exists on the `WorkChain` base class. Usually the `super` is not called in this scenario and so the functionality is broken. The typical example is where the user uses the `run` method as a step in the outline of the `WorkChain`. The work chain will still run, however, only that one step in the outline is called. Since the logic to continue to the next step in the outline is defined in `WorkChain.run`, which is overridden and now no longer called, the rest of the work chain is skipped without any warning or error message, leaving the user scratching their head as to what happened. Here we protect this and other public methods on the `WorkChain` class to prevent them from being overridden in subclasses. This is accomplished by adding the `Protect` class as a metaclass. Since the `WorkChain` already has the metaclass `plumpy.ProcessStateMachineMeta`, which it inherits from its `Process` base class, and all metaclasses need to share the same base, `Protect` also subclasses the `ProcessStateMachineMeta` class. The `Protect` class provides the `final` classmethod which can be used to decorate a method in the `WorkChain` class that should be protected. If a subclass implements it, as soon as the class is imported, a `RuntimeError` is raised mentioning that the method cannot be overridden. The test `test_report_dbloghandler` had to be fixed because it actually suffered from the very problem that is being fixed. It used the `run` method to setup the test, but since the `check` was never being called, the test always passed, even though the code `self._backend` in the `check` is incorrect.
902a95b
to
bb67487
Compare
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.
@sphuber thanks!
Can you explain why has to create Protect
class rather than using @typing.final
directly, I think the reason is you make it a runtime check here. But didn't understand the full trick here.
If I understand correctly, the |
Indeed, the |
Not quite. The public methods of |
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 the explanation! All good to me.
Fixes #5467
The paradigm of the
WorkChain
requires a user in an implementation to define the workflow logic through classmethods of theWorkChain
subclass. While this gives great flexibility and choice to the user, there is a risk that a user inadvertently chooses a method name that already exists on theWorkChain
base class. Usually thesuper
is not called in this scenario and so the functionality is broken.The typical example is where the user uses the
run
method as a step in the outline of theWorkChain
. The work chain will still run, however, only that one step in the outline is called. Since the logic to continue to the next step in the outline is defined inWorkChain.run
, which is overridden and now no longer called, the rest of the work chain is skipped without any warning or error message, leaving the user scratching their head as to what happened.Here we protect this and other public methods on the
WorkChain
class to prevent them from being overridden in subclasses. This is accomplished by adding theProtect
class as a metaclass. Since theWorkChain
already has the metaclassplumpy.ProcessStateMachineMeta
, which it inherits from itsProcess
base class, and all metaclasses need to share the same base,Protect
also subclasses theProcessStateMachineMeta
class.The
Protect
class provides thefinal
classmethod which can be used to decorate a method in theWorkChain
class that should be protected. If a subclass implements it, as soon as the class is imported, aRuntimeError
is raised mentioning that the method cannot be overridden.The test
test_report_dbloghandler
had to be fixed because it actually suffered from the very problem that is being fixed. It used therun
method to setup the test, but since thecheck
was never being called, the test always passed, even though the codeself._backend
in thecheck
is incorrect.