Skip to content

Conversation

danielhollas
Copy link
Collaborator

@danielhollas danielhollas commented Jan 14, 2025

Run typing on orm/comments.py, orm/computers.py and orm/implementation/storage_backend.py. These files were mostly typed already, but not checked by mypy.

Depends on #6703

Copy link

codecov bot commented Jan 14, 2025

Codecov Report

❌ Patch coverage is 88.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.27%. Comparing base (ebc9d4a) to head (8e5f4d3).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/aiida/orm/comments.py 80.00% 1 Missing ⚠️
src/aiida/orm/computers.py 87.50% 1 Missing ⚠️
src/aiida/orm/entities.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6704      +/-   ##
==========================================
- Coverage   79.27%   79.27%   -0.00%     
==========================================
  Files         566      566              
  Lines       43779    43782       +3     
==========================================
+ Hits        34700    34702       +2     
- Misses       9079     9080       +1     

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

@danielhollas danielhollas marked this pull request as ready for review January 14, 2025 16:13
def list_labels(self) -> List[str]:
"""Return a list with all the labels of the computers in the DB."""
return self._backend.computers.list_names()
return self._backend.computers.list_names() # type: ignore[attr-defined]
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 why mypy complains here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

src/aiida/orm/computers.py:57: error: "BackendComputerCollection" has no attribute "list_names" [attr-defined]

checking for a valid scheduler.
"""
if not isinstance(mpirun_cmd, (tuple, list)) or not all(isinstance(i, str) for i in mpirun_cmd):
if not isinstance(mpirun_cmd, (tuple, list)) or not all(isinstance(i, str) for i in mpirun_cmd): # type: ignore[redundant-expr]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Mypy complains that the isinstance check should be always true since the mpirun_cmd is typed at list or tuple, but we obviously want to preserve the runtime check since we don't control all the callers.

Copy link
Member

Choose a reason for hiding this comment

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

Or I think the better change would be we move the mpirun_cmd runtime check when set up the computer? But it seems out the scope of this PR.

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 check is in the _mpirun_command_validator which is called by the validate function which is in turn called before storing the Computer instance in DB (in function store()). So I think the code here is in the correct place.

@unkcpz
Copy link
Member

unkcpz commented Jan 14, 2025

would be good to get reviewed by someone familiar with the orm implementation.

Then my review will not count ;p

Just out of curious, the goal of the PR is to add more mypy checking to code base (I am totally in it, thanks! I was thinking maybe we can even have it as a project for this year's GSoC to find someone can working on it ) or you are targeting to bring some new features that may benefit from adding type?

@danielhollas
Copy link
Collaborator Author

Then my review will not count ;p

Haha, fair. I am mostly confused about this code:

class Comment(entities.Entity['BackendComment', CommentCollection]):

Mypy complains about the missing BackendComment type, but when I import it, I get an error from ruff that the import is unused. I think it might be a limitation of ruff so I just added a noqa comment.

@danielhollas
Copy link
Collaborator Author

danielhollas commented Jan 14, 2025

Just out of curious, the goal of the PR is to add more mypy checking to code base (I am totally in it, thanks! I was thinking maybe we can even have it as a project for this year's GSoC to find someone can working on it ) or you are targeting to bring some new features that may benefit from adding type?

Just adding type checking. These files just seemed like low-hanging fruit, they were already mostly typed!

What I really don't like is that we have a list of ignored types in pre-commit config file. It's very easy to miss, and to mess with types in files that are not even checked.

I think it would be better to have type: ignore comments at the top of the ignored files, which makes it super clear that they are not checked.

EDIT: What originally prompted me was the other PR which add checking for the orm/caching file since I was looking at it while working on aiida-test-cache.

@danielhollas danielhollas requested a review from unkcpz January 14, 2025 16:47
@unkcpz
Copy link
Member

unkcpz commented Jan 14, 2025

Mypy complains about the missing BackendComment type, but when I import it, I get an error from ruff that the import is unused.

How you import the BackendComment? Since in the typing annotation, you use single-quotes you need to import:

if typing.TYPE_CHECKING:
    from aiida.xxx import BackendComment

@unkcpz
Copy link
Member

unkcpz commented Jan 14, 2025

I think it would be better to have type: ignore comments at the top of the ignored files, which makes it super clear that they are not checked.

I agree, but it is not easy to make typing correct in python. No actually compiler will help us (yes, I am comparing it with Rust 🙈 ). But I am also trying to bring typing to engine part of the code base module by module (like this one #6641).

@danielhollas
Copy link
Collaborator Author

How you import the BackendComment? Since in the typing annotation, you use single-quotes you need to import:

Yes, that's what I am doing in this PR.

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.

I gave my go on this PR, but it would be nice that someone more familiar with orm can give it a look. I pin @edan-bainglass because your name is in the storage part of team responsibilities.

@danielhollas
Copy link
Collaborator Author

@edan-bainglass after the pydantic PR was merged, I needed to add a bunch of extra type: ignore comments here. Could you please take a look and see if they can be solved (and that they don't point out real bugs)?

c3bca2c

@danielhollas danielhollas marked this pull request as ready for review September 28, 2025 19:01
@danielhollas danielhollas requested a review from unkcpz September 28, 2025 19:27
is_attribute=False,
orm_class='core.node',
orm_to_model=lambda comment, _: comment.node.pk,
orm_to_model=lambda comment, _: cast('Comment', comment).node.pk,
Copy link
Member

Choose a reason for hiding this comment

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

For context if unclear, orm_to_model is used when converting an ORM object to its model via .to_model(). The model should be kept serializable, so AiiDA entities are often converted to their pk value. In this case, that would be the pk of the Node associated with the Comment instance of this Model. The Model does not have a reference of its owning Comment instance, so a validator cannot be used. Hence (I presume), it motivates the custom orm_to_model feature.

Copy link
Member

Choose a reason for hiding this comment

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

The lambda function doesn't know what comment is (or whatever the entity is in any use), hence the need for casting.

Copy link
Member

Choose a reason for hiding this comment

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

Regarding the second argument (which is, I believe, ignored in general), we pass a repository path. I vaguely recall @agoscinski implementing this. If so, @agoscinski could you please briefly explain why, or reference to an existing explanation? 🙏

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@edan-bainglass thank you for the context, that is helpful. 🙏

Comment on lines 79 to +81
def create( # type: ignore[override]
self, node: 'BackendNode', user: 'BackendUser', content: Optional[str] = None, **kwargs
):
self, node: 'BackendNode', user: 'BackendUser', content: Optional[str] = None, **kwargs: Any
) -> BackendComment:
Copy link
Member

Choose a reason for hiding this comment

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

Technically breaks Liskov substitution principle.

Copy link
Member

Choose a reason for hiding this comment

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

One solution would be

@abc.abstractmethod
def create(self, 
    node: 'BackendNode' | None = None,
    user: 'BackendUser' | None = None,
    content: Optional[str] = None,
    **kwargs: Any
) -> BackendComment:
...

and explicitly check that node and user exist, complaining otherwise. It will appease Liskov (no required kwargs, as stated in the abstract class), and mypy by extension (hopefully). But it may confuse the API user - we state they are optional... they are not!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@edan-bainglass yeah thanks for pointing this out. I think I prefer to ignore the mypy error here instead of complicating the function signature (at least for this PR). Perhaps a more holistic fix is needed for this.

btw: Here's the mypy error

src/aiida/orm/implementation/comments.py:79: error: Signature of "create" incompatible with supertype "aiida.orm.implementation.entities.BackendCollection"  [override]
src/aiida/orm/implementation/comments.py:79: note:      Superclass:
src/aiida/orm/implementation/comments.py:79: note:          def create(self, **kwargs: Any) -> BackendComment
src/aiida/orm/implementation/comments.py:79: note:      Subclass:
src/aiida/orm/implementation/comments.py:79: note:          def create(self, node: BackendNode, user: BackendUser, content: str | None = ..., **kwargs: Any) -> BackendComment

Copy link
Member

Choose a reason for hiding this comment

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

Yep, that's an LSP violation. We can have a look at a "better" solution later as you say


_CLS_COLLECTION: Type[CollectionType] = Collection # type: ignore[assignment]
_logger = log.AIIDA_LOGGER.getChild('orm.entities')
_logger: Logger = log.AIIDA_LOGGER.getChild('orm.entities')
Copy link
Member

Choose a reason for hiding this comment

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

Don't you need to do 'Logger' if imported under TYPE_CHECKING?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There is from __future__ import annotations at the top so all types are implicitly converted to strings.

Copy link
Member

Choose a reason for hiding this comment

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

I think perhaps we are inconsistent about this throughout the code. Likely, from __future__ import annotations is added to a module at some point but 'SomeType' is never reduced to SomeType. Maybe ruff can auto-fix?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Relevant ruff issue:
astral-sh/ruff#18502

Copy link
Member

@edan-bainglass edan-bainglass Sep 30, 2025

Choose a reason for hiding this comment

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

Thanks. Regarding the inconsistency, I think already in this PR they exist - see above change to comments.py (the casting in orm_to_model lambdas).

Copy link
Member

@edan-bainglass edan-bainglass left a comment

Choose a reason for hiding this comment

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

Added a few comments. In general, I'm happy with this, but maybe we can have a call to understand the final goal (the typing mission), and order of operations 🙂

@danielhollas
Copy link
Collaborator Author

@edan-bainglass thank you for the review. I think I addresses all your comments, PTAL.

but maybe we can have a call to understand the final goal (the typing mission), and order of operations

Sure, maybe we can talk on Thursdays aiida-core meeting (or after?).

tl;dr on my side, I was adding typing to aiida-core just as a side-project for me to learn more about Python typing system. Besides merging the existing PRs I don't have any immediate plans to work on this further in the near future (this has already consumed too much time I should have been spending elsewhere 😅)

FWIW: I think the biggest priority (and what I have been doing) is the fact that many files are skipped altogether from type checking (see the list in .pre-commit-config.yaml) which compromises the typing of the whole codebase and is super confusing since many of the skipped files actually contains some type annotations, which may therefore be wrong since they are not checked).

@edan-bainglass
Copy link
Member

@edan-bainglass thank you for the review. I think I addresses all your comments, PTAL.

but maybe we can have a call to understand the final goal (the typing mission), and order of operations

Sure, maybe we can talk on Thursdays aiida-core meeting (or after?).

Let's do that, just to sync up.

tl;dr on my side, I was adding typing to aiida-core just as a side-project for me to learn more about Python typing system. Besides merging the existing PRs I don't have any immediate plans to work on this further in the near future (this has already consumed too much time I should have been spending elsewhere 😅)

No worries. I will pick it up, but likely limited to the ORM.

FWIW: I think the biggest priority (and what I have been doing) is the fact that many files are skipped altogether from type checking (see the list in .pre-commit-config.yaml) which compromises the typing of the whole codebase and is super confusing since many of the skipped files actually contains some type annotations, which may therefore be wrong since they are not checked).

Indeed, needs to be addressed.

Copy link
Member

@edan-bainglass edan-bainglass 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 for the replies. Good on my end. Let's go!

@danielhollas danielhollas merged commit f6cdb91 into aiidateam:main Sep 29, 2025
16 checks passed
@danielhollas danielhollas deleted the comments-typing branch September 29, 2025 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants