-
Notifications
You must be signed in to change notification settings - Fork 229
Bump mypy to 1.16.0 #6858
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
Bump mypy to 1.16.0 #6858
Conversation
@@ -122,17 +122,17 @@ def profile_setup(): | |||
"""Set up a new profile.""" | |||
|
|||
|
|||
@verdi_profile.command('configure-rabbitmq') # type: ignore[arg-type] | |||
@verdi_profile.command('configure-rabbitmq') | |||
@click.pass_context |
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 quite sure why, but moving pass_context
here fixed the following error:
src/aiida/cmdline/commands/cmd_profile.py:135: error: Argument 1 to "pass_context" has incompatible type "Callable[[Any, Any, Any, Any, KwArg(Any)], Any]"; expected "Callable[[Context, Any, Any, Any, KwArg(Any)], Any]" [arg-type]
src/aiida/cmdline/commands/cmd_profile.py:135: note: This is likely because "profile_configure_rabbitmq" has named arguments: "ctx". Consider marking them positional-only
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 guess @verdi_profile.command()
change the function signature and make @click.pass_context lose track on what need to wrap.
If switch works and not break verdi profile
tests then should be okay.
UPDATE: sorry, I didn't see the full change, I thought the @click.pass_context
was moved as the first layer of wrapper.
I'd suggest not change the order of decorators, the @click.pass_context
should be the decorator that directly applied to the function to get the ctx
. If there are other decorators in the middle that mutate ctx
I assume it may cause unexpected behavior.
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.
You forgot this one. Maybe not change the order, change the order of decorator change the behavior, correct?
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.
Ah, yes, thanks for the remainder. As you noticed, in the end I just read the error message more carefuly and marked ctx as a position-only argument. I am not sure why it is necessary here, and not in the other places? Perhaps because we have the **kwargs
here. But in any case, this feels like a safe change.
except Exception as exception: | ||
LOGGER.warning(f'function `{func.__name__}` has a docstring that could not be parsed: {exception}') | ||
param_help_string = {} | ||
if func.__doc__ is 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.
This fixes:
src/aiida/engine/processes/functions.py:377: error: Argument 1 to "parse" has incompatible type "str | None"; expected "str" [arg-type]
Github is very confused, the diff here is actually not that bad, we're just handling the func.__doc__ is None
case pre-emptively. (We could also log a warning if a calcfunction is missing a docstring, but that seems overly pedantic)
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.
We could also log a warning if a calcfunction is missing a docstring, but that seems overly pedantic
Yes, I think that is a bit too much, I won't throw warning for that.
@@ -435,7 +439,7 @@ def define(cls, spec): | |||
def indirect_default(value=default): | |||
return to_aiida_type(value) | |||
else: | |||
indirect_default = default | |||
indirect_default = default # type: ignore[assignment] |
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 super clear how to solve this one, since indirect_default
can be basically anything
src/aiida/engine/processes/functions.py:438: error: Incompatible types in assignment (expression has type "Any | tuple[()]", variable has type "Callable[[Any], Any]") [assignment]
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.
If it can be anything then as error message suggest use Callable[[Any], Any]
?
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 am not sure what are you suggesting here? the callable type is inferred because indirect_default
is defined as a function in the if branch above, but in the else branch in can be anything. I think this we could squash this by inverting the logic and put the else branch first, but I don't really want to touch the logic.
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.
Okay, I misunderstood the change here.
I think the mypy complain because the typechecker resolve the expression in if..else.. branch and want to make sure the type of expression conform with each other.
Then the correct way to solve this might be for the else branch do:
def indirect_default(value=default):
return lambda _: value
(I am not sure this is correct, but I think it is better to just ignore the type error here to not touch the logic. so if you don't want to try please ignore my comment.)
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #6858 +/- ##
==========================================
+ Coverage 79.14% 79.15% +0.01%
==========================================
Files 565 565
Lines 43391 43395 +4
==========================================
+ Hits 34339 34343 +4
Misses 9052 9052 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Note to me: There seems to be also mypy fixes here #6641 Need to check this too. I think we first merge this PR and then rebase the other |
I approve the changes. Just waiting till we release v2.7.0 before merge |
src/aiida/cmdline/groups/dynamic.py
Outdated
@@ -137,7 +137,7 @@ def apply_options(func): | |||
shared_options.reverse() | |||
|
|||
for option in shared_options: | |||
func = option(func) | |||
func = option(func) # type: ignore[operator] |
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 understand why this is needed all of a sudden, especially given that we have basically identical code couple lines above that apparently is fine. mypy error:
src/aiida/cmdline/groups/dynamic.py:140: error: "Option" not callable [operator]
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'll be easy to check if you have inlayhint in your IDE to inspect the type of options_list
and shared_options
.
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.
Took me a while to understand the code, but in the end the mypy error was caused by a wrong type annotation of shared_options
in the class constructor, see commit 05df184
@@ -231,7 +231,7 @@ def get_code_helper(cls, label, machinename=None, backend=None): | |||
return result[0] | |||
|
|||
@classmethod | |||
def get(cls, pk=None, label=None, machinename=None): | |||
def get(cls, pk=None, label=None, machinename=None): # type: ignore[override] |
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 understand why mypy complains here:
src/aiida/orm/nodes/data/code/legacy.py:234: error: Signature of "get"
incompatible with supertype "Entity" [override]
def get(cls, pk=None, label=None, machinename=None):
^
src/aiida/orm/nodes/data/code/legacy.py:234: note: Superclass:
src/aiida/orm/nodes/data/code/legacy.py:234: note: @classmethod
src/aiida/orm/nodes/data/code/legacy.py:234: note: def get(cls, **kwargs: Any) -> Any
src/aiida/orm/nodes/data/code/legacy.py:234: note: Subclass:
src/aiida/orm/nodes/data/code/legacy.py:234: note: @classmethod
src/aiida/orm/nodes/data/code/legacy.py:234: note: def get(cls, pk: Any = ..., label: Any = ..., machinename: Any = ...) -> Any
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.
Since you asked, my answer is because we did bad design by keep on doing too much bad inheritance.
I'd just add an ignore
, otherwise need to add an other function signature dispatch or change the signature of parent function which can cause unexpect things.
4db11f8
to
ee2e443
Compare
@unkcpz can you take a look? Now that 2.7.0 is out it would be great to get this merged to unblock other typing PRs. |
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.
@danielhollas thanks. I give my go, just some minor requests.
src/aiida/cmdline/groups/dynamic.py
Outdated
@@ -137,7 +137,7 @@ def apply_options(func): | |||
shared_options.reverse() | |||
|
|||
for option in shared_options: | |||
func = option(func) | |||
func = option(func) # type: ignore[operator] |
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'll be easy to check if you have inlayhint in your IDE to inspect the type of options_list
and shared_options
.
@@ -435,7 +439,7 @@ def define(cls, spec): | |||
def indirect_default(value=default): | |||
return to_aiida_type(value) | |||
else: | |||
indirect_default = default | |||
indirect_default = default # type: ignore[assignment] |
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.
If it can be anything then as error message suggest use Callable[[Any], Any]
?
@@ -47,7 +47,7 @@ class Model(AbstractCode.Model): | |||
..., | |||
title='Computer', | |||
description='The remote computer on which the executable resides.', | |||
orm_to_model=lambda node, _: node.computer.label, | |||
orm_to_model=lambda node, _: node.computer.label, # type: ignore[attr-defined] |
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.
If we know what type of label or type of node.computer
should be, in principle can use cast
?
lambda node, _: cast(orm.Computer, node.computer).label
?
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 looks like the following works
orm_to_model=lambda node, _: node.computer.label, # type: ignore[attr-defined] | |
orm_to_model=lambda node, _: cast('InstalledCode', node).computer.label, |
@@ -231,7 +231,7 @@ def get_code_helper(cls, label, machinename=None, backend=None): | |||
return result[0] | |||
|
|||
@classmethod | |||
def get(cls, pk=None, label=None, machinename=None): | |||
def get(cls, pk=None, label=None, machinename=None): # type: ignore[override] |
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.
Since you asked, my answer is because we did bad design by keep on doing too much bad inheritance.
I'd just add an ignore
, otherwise need to add an other function signature dispatch or change the signature of parent function which can cause unexpect things.
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 review @unkcpz ! I believe I addressed all your comments.
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 @danielhollas . Just one comment about the order of where to put@click.pass_context
that you may overlooked? I'd suggest not touch the order, if you are not 100% confident that will not break anything.
@@ -122,17 +122,17 @@ def profile_setup(): | |||
"""Set up a new profile.""" | |||
|
|||
|
|||
@verdi_profile.command('configure-rabbitmq') # type: ignore[arg-type] | |||
@verdi_profile.command('configure-rabbitmq') | |||
@click.pass_context |
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.
You forgot this one. Maybe not change the order, change the order of decorator change the behavior, correct?
except Exception as exception: | ||
LOGGER.warning(f'function `{func.__name__}` has a docstring that could not be parsed: {exception}') | ||
param_help_string = {} | ||
if func.__doc__ is 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.
We could also log a warning if a calcfunction is missing a docstring, but that seems overly pedantic
Yes, I think that is a bit too much, I won't throw warning for that.
@@ -435,7 +439,7 @@ def define(cls, spec): | |||
def indirect_default(value=default): | |||
return to_aiida_type(value) | |||
else: | |||
indirect_default = default | |||
indirect_default = default # type: ignore[assignment] |
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.
Okay, I misunderstood the change here.
I think the mypy complain because the typechecker resolve the expression in if..else.. branch and want to make sure the type of expression conform with each other.
Then the correct way to solve this might be for the else branch do:
def indirect_default(value=default):
return lambda _: value
(I am not sure this is correct, but I think it is better to just ignore the type error here to not touch the logic. so if you don't want to try please ignore my 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.
@danielhollas thanks!
@unkcpz thanks for the thorough review! @agoscinski I am planning to merge this once the tests are finished, since you gave this a green light already, but let me know if you have any concerns. (also let me know if you don't want to merge any PRs :-) but this one is mostly just typing so relatively low risk) |
This is a nice bump since we can remove a lot of
type: ignore
comments!