Skip to content

Conversation

danielhollas
Copy link
Collaborator

This is a nice bump since we can remove a lot of type: ignore comments!

@@ -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
Copy link
Collaborator Author

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

Copy link
Member

@unkcpz unkcpz Jul 1, 2025

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.

Copy link
Member

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?

Copy link
Collaborator Author

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:
Copy link
Collaborator Author

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)

Copy link
Member

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]
Copy link
Collaborator Author

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]

Copy link
Member

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] ?

Copy link
Collaborator Author

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.

Copy link
Member

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.)

Copy link

codecov bot commented May 6, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 79.15%. Comparing base (a392f5c) to head (3734865).
Report is 1 commits behind head on main.

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.
📢 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.

@agoscinski
Copy link
Contributor

agoscinski commented May 6, 2025

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

@agoscinski
Copy link
Contributor

I approve the changes. Just waiting till we release v2.7.0 before merge

@danielhollas
Copy link
Collaborator Author

I think we first merge this PR and then rebase the other

Yes, I think merging this first makes sense since clearly mypy got better at inferring some types. I also have three typing PRs in progress, #6833 #6707 and #6704, which I will rebase once this is merged.

@danielhollas danielhollas changed the title Bump mypy to 1.15.0 Bump mypy to 1.16.0 Jun 3, 2025
@@ -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]
Copy link
Collaborator Author

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]

Copy link
Member

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.

Copy link
Collaborator Author

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]
Copy link
Collaborator Author

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

Copy link
Member

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.

@danielhollas
Copy link
Collaborator Author

@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.

Copy link
Member

@unkcpz unkcpz left a 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.

@@ -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]
Copy link
Member

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]
Copy link
Member

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]
Copy link
Member

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?

Copy link
Collaborator Author

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

Suggested change
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]
Copy link
Member

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.

Copy link
Collaborator Author

@danielhollas danielhollas 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 the review @unkcpz ! I believe I addressed all your comments.

@danielhollas danielhollas requested a review from unkcpz July 2, 2025 10:46
Copy link
Member

@unkcpz unkcpz left a 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
Copy link
Member

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:
Copy link
Member

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]
Copy link
Member

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.)

Copy link
Member

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

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

@danielhollas thanks!

@danielhollas
Copy link
Collaborator Author

@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)

@danielhollas danielhollas merged commit 2106844 into aiidateam:main Jul 4, 2025
32 checks passed
@github-project-automation github-project-automation bot moved this from In review to Done in aiida-core v2.7.1 Jul 4, 2025
@danielhollas danielhollas deleted the update-mypy branch July 4, 2025 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants