-
Notifications
You must be signed in to change notification settings - Fork 141
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
add hook validation logic #706
Conversation
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.
Awesome to see this, really excited about this one.
I did a quick pass only. Just a few remarks questions
- do you want to start adding tests, or do you want me to help with those?
- should we warn instead of throw, so that we do not raise on false positives>
- if we don't have the above, should we have an opt out way, something like with flake/ruffs noqa?
solara/hooks/validate_hooks.py
Outdated
DEFAULT_USE_FUNCTIONS = ( | ||
"use_state", | ||
"use_reactive", | ||
"use_thread", | ||
"use_task", | ||
"use_effect", | ||
"use_memo", | ||
) |
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 we can assume all use_
functions are hooks. Also hooks (use_* functions) have to obey these rules.
What is the minimum supported python version? I don't see an explicit minimum on pypi. It will tell me what syntax should be expected. Another case that came up in my code is |
Yes, although we have to see/think if this is a 'breaking' change or not. With solara 2.0 we can absolutely do this, so we either add this feature in solara 2.0, or warn in version <2, and raise in >= 2.
Love this, I'll need to dive a bit into what the best syntax is here. I also think that when we raise, we should give this information as well, so we don't force people to search/google this.
Python 3.6 (we see large enterprises being stuck on this version still.. ), but we should have this in our pyproject.toml to be explicit I think. PS: I'll look into the typing issues later
I agree, I'm ok with raising on it, and fix this if it comes up, and a user has a good reason for it. |
86646f5
to
df2fd66
Compare
- Handle `try` statements - Catch all generic `use_*` statements
Based on your earlier comments, my latest updates check for:
Are there other fundamental "no-go" situations that lead to unstable reactive states? Or is everything theoretically covered now? To handle false positives in the meantime, since
@solara.component(ignore_unstable_use: list[InvalidReactivityCause] | InvalidReactivityCause)
def MyComponent(): ...
@solara.component
@validate_hooks(ignore_causes=[...])
def MyComponent(): ...
This also gives us time to workshop the line-level suppression syntax without delaying the feature entirely. It will also be backwards-compatible when enabled. |
424378c
to
151fc4b
Compare
I hope you like 729807d which makes it easier to find the line in your editor back. Also, I think we should be using Looking at a similar project, https://github.com/reactive-python/reactpy-flake8 we could choose some prefix names if we do not want to opt out completely of linters. Example # single line
@solara.component
def Page():
for i in range(10):
solara.use_state(1) # noqa
solara.Text("Done")
# whole function
@solara.component
def Page(): # noqa
for i in range(10):
solara.use_state(1)
solara.Text("Done") For specific opt outs: # single line
@solara.component
def Page():
for i in range(10):
solara.use_state(1) # noqa: SH100
solara.Text("Done")
# whole function
@solara.component
def Page(): # noqa
for i in range(10):
solara.use_state(1) # noqa: SH100
solara.Text("Done") I think we can use the settings system with environment variables to control what the checks do What do you think? |
151fc4b
to
253f424
Compare
253f424
to
738fa53
Compare
738fa53
to
2d0cf74
Compare
Feel free to push back on my changes, that I push code does not mean that this is the way it should be. I think I want to go over https://react.dev/reference/rules/rules-of-hooks and put those in our docs, see if they are all compatible with the code, and make sure our tests are in line with them. |
To my knowledge,
This sounds great to me! But I lean toward not supporting an Also great idea. Nice to know the current code state covers all these cases! Even answering the |
otherwise the hook validation code will fail to find the sourcecode for a component.
I now check for # on a line without an external library, it might find false positives (a # noq in a string will also be found) but that should not be a problem I think.
Solara 2.0? I really don't want to break existing users, without giving them an escape hatch. |
My instinct is that The reason I am leaning strongly in this direction is I foresee some users putting That's my take, but your reason for providing All to say, I respectfully disagree that users need an |
Removing off is I think ok with a 2.0 release, but too much of a breaking change for < 2. We could open an issue for 2.0 milestone so we do not forget about it. What do you think? Also, I'm not sure what the The only thing left to do is to write the docs for this, so https://solara.dev/documentation/advanced/understanding/rules-of-hooks explains the why, and that the off/warn/raise options are possible. I hope you like the error msg, e.g.:
|
PS: I was worried about performance, but starting up the solara docs made it only a few percentage slower, I don't think it is needed to disable it in production mode. |
Works for me!
Consider this case: my_use = solara.use_thread
if True:
return
my_use(...) It would be difficult to detect these cases unless we do full syntactic tracing of variable assignments. Alternatively, if VARIABLE_ASSIGNMENt of a Similar to the statements earlier about
Looks great to me! |
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 comments. The only potential additional "work item" would be if use_*
inside generators and comprehensions should also be identified
Otherwise, it looks good to me!
solara/validate_hooks.py
Outdated
elif isinstance(func, ast.Attribute): | ||
id_ = func.attr | ||
else: | ||
raise ValueError(f"Unexpected function node type: {func}, line={node.lineno}") |
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.
Is there any way for us not to error for the user here, but see whether this case occurs? Perhaps there is a valid ast.Call
case I am missing, and if we raise a value error here, the user may not get an informative message of how to move forward
Would it violate user privacy too badly if we add a telemetry metric to log just the ast node type that occurred here, rather than raise a value error?
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.
According to https://docs.python.org/3/library/ast.html#ast.Call:
func is the function, which will often be a Name or Attribute object
"Often" doesn't say what the exhaustive possibilities include...
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 it's better we do a warnings.warn
call, so we don't break code because of things we don't know that happen in real life code. Hopefully users who see the warning will inform us, and we can handle it. But I agree we should not break code.
self._visit_children_using_scope(node) | ||
|
||
def visit_For(self, node: ast.For): | ||
self._visit_children_using_scope(node) |
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's unlikely for users to do this, but visit_
ListComp
, SetComp
, etc. should also be defined if we want to be rigorous
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.
Good call, I opened #725 so we can get this PR in
solara.validate_hooks.InvalidReactivityCause.CONDITIONAL_USE, | ||
solara.validate_hooks.InvalidReactivityCause.LOOP_USE, | ||
} | ||
|
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.
Perhaps add checks for
def foo(x, # noqa: SH102
y
):
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.
done
tests/unit/hook_use_invalid_test.py
Outdated
# with hook_check_raise(), pytest.raises(HookValidationError): | ||
# @solara.component | ||
# def Page(): | ||
# use_lala = 1 |
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.
# use_lala = 1 | |
# my_use = solara.use_state | |
# if 1 < 3: | |
# my_use(1) |
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'd really like to get this in, so once green, I'll merge this. We can always improve the docs, and extend with #725
self._visit_children_using_scope(node) | ||
|
||
def visit_For(self, node: ast.For): | ||
self._visit_children_using_scope(node) |
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.
Good call, I opened #725 so we can get this PR in
solara.validate_hooks.InvalidReactivityCause.CONDITIONAL_USE, | ||
solara.validate_hooks.InvalidReactivityCause.LOOP_USE, | ||
} | ||
|
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.
done
Supersedes widgetti/reacton#37.
Checks at compile-time whether the following invalid usage of hooks occurs:
See some tests:
Test cases source code
Outputs: