Skip to content
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

Enable PyQt5 type checking #1611

Merged
merged 24 commits into from
Jan 5, 2023
Merged

Enable PyQt5 type checking #1611

merged 24 commits into from
Jan 5, 2023

Conversation

gonzalo-bulnes
Copy link
Contributor

@gonzalo-bulnes gonzalo-bulnes commented Dec 14, 2022

Description

We are currently not providing PyQt5 stubs to mypy. As a result, Qt types are not checked. Since half of the repo is a Qt GUI, we should fix that.

@zekehuntergreen wrote (source):

After reading the section in the docs on None and Optional handling [the fact that mypy didn't identify the implicit Optional[pyqtSignal]] surprised me too! It sounds like the default behavior changed as of version 0.980 so that parameters with None as a default value are no longer treated as Optional be default.

I think what's going on is that mypy can't find a library stub for PyQt5.QtCore so in the absence of a type for pyqtSignal, it's being treated as Any. Since None is a subtype of Any, mypy doesn't raise an "incompatible default for argument" error.
It looks like there is a stub library for PyQt5 which it might be worth looking into adding to the project!

This PR adds the library to the development requirements and fixes the issues highlighted by make mypy.

Towards #1614

Test Plan

🔮 Reviewers: It makes a lot of sense to review this PR commit by commit. They're independent form each other, and I've added to the commit message when making the most significant decisions.

The gist of it is:

  • Confirm that the tests haven't changed significantly
  • Confirm that the type checks that were ignored can reasonably be ignored
  • Confirm that there are no regressions in:
    • The export flow
    • ... please add any feature that seems relevant to the files that have been touched.

@gonzalo-bulnes gonzalo-bulnes self-assigned this Dec 14, 2022
@gonzalo-bulnes gonzalo-bulnes added the ⚙️ Tooling Improving maintainability and increasing maintainer joy : ) label Dec 14, 2022
@gonzalo-bulnes gonzalo-bulnes force-pushed the enable-pyqt-type-checking branch 4 times, most recently from 57daf93 to 636dca0 Compare December 15, 2022 05:48
@gonzalo-bulnes gonzalo-bulnes changed the title Refactor enable PyQt5 type checking Enable PyQt5 type checking Dec 15, 2022
@gonzalo-bulnes gonzalo-bulnes force-pushed the enable-pyqt-type-checking branch from beb125e to ff17311 Compare December 20, 2022 09:52
All credits go to @zekehuntergreen for finding about it!
I don't think we currently manipulate any unbound signals anywhere!

That's a great insight provided by mypy's type checks :)
By default, Qt picks the a Qt.QueuedConnection automatically
whenever needed.

Explicit queued connections also happen to make testing harder,
and mypy singles out the type parameter as unexpected.

See https://doc.qt.io/qt-5/qt.html#ConnectionType-enum
Note how the tests are asserting on private attibutes and mocking
the object under test. I am not fixing that at this time.
I didn't find how to hint mypy on the type of conversation_view.
The alternative would be to define a QApplication subclass, which
doesn't seem worth it at this point.
Assertions allow to narrow the type of an ambiguous object.
See https://mypy.readthedocs.io/en/stable/type_narrowing.html
Assertions allow to narrow the type of an ambiguous object.
See https://mypy.readthedocs.io/en/stable/type_narrowing.html
Assertions allow to narrow the type of an ambiguous object.
See https://mypy.readthedocs.io/en/stable/type_narrowing.html
Assertions allow to narrow the type of an ambiguous object.
See https://mypy.readthedocs.io/en/stable/type_narrowing.html

Note: the test that was modified contains two smells:
- it mocks a type to assert on its __init__ method (should probably
  assert on the resulting instance)
- the specification of the mock as a Messagewidget isn't enough to
  satisfy the code requirements...

I am leaving both those issues unsolved because I think the entire
test needs re-thinking and that is out of scope of the PR. Meanwhile,
type annotations are useful immediately.
Assertions allow to narrow the type of an ambiguous object.
See https://mypy.readthedocs.io/en/stable/type_narrowing.html
@gonzalo-bulnes gonzalo-bulnes force-pushed the enable-pyqt-type-checking branch from ff17311 to 51609f9 Compare December 20, 2022 10:08
@gonzalo-bulnes gonzalo-bulnes marked this pull request as ready for review December 20, 2022 10:09
@gonzalo-bulnes gonzalo-bulnes requested a review from a team as a code owner December 20, 2022 10:09
@gonzalo-bulnes
Copy link
Contributor Author

gonzalo-bulnes commented Dec 20, 2022

@rocodes @eaon @cfm PSA: I'm planning on porting this to the export and updater repos. 📣

@gonzalo-bulnes
Copy link
Contributor Author

gonzalo-bulnes commented Dec 20, 2022

@zekehuntergreen For some reason, I don't seem to be able to request a review from you through GitHub's UI, but any thoughts or comments from you would be welcome!

@cfm cfm self-assigned this Dec 21, 2022
Copy link
Contributor

@zekehuntergreen zekehuntergreen left a comment

Choose a reason for hiding this comment

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

Thanks so much for adding this stub!
The note to review commit by commit was very useful 👌

securedrop_client/gui/base/misc.py Outdated Show resolved Hide resolved
securedrop_client/api_jobs/base.py Show resolved Hide resolved
securedrop_client/gui/widgets.py Show resolved Hide resolved
securedrop_client/gui/widgets.py Outdated Show resolved Hide resolved
Copy link
Member

@cfm cfm left a comment

Choose a reason for hiding this comment

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

These changes look great to me, @gonzalo-bulnes. I've left two considerations in pending discussions, after which I'll be happy to approve and merge.

securedrop_client/gui/widgets.py Show resolved Hide resolved
securedrop_client/gui/widgets.py Outdated Show resolved Hide resolved
Assertions are not checked in production, and are risky to use
for any reason that relies on them at runtime.

Type checking is not a runtime concern, and using assertions
to narrow types during type checks is not a problem.

See https://mypy.readthedocs.io/en/stable/type_narrowing.html
and https://snyk.io/blog/the-dangers-of-assert-in-python/
Copy link
Member

@cfm cfm left a comment

Choose a reason for hiding this comment

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

Looks good to me, @gonzalo-bulnes! And I've confirmed that fc3afca works as intended by pushing a branch with this patch, which failed as expected:

--- a/securedrop_client/gui/widgets.py
+++ b/securedrop_client/gui/widgets.py
@@ -2338,7 +2338,7 @@ class FileWidget(QWidget):
     def eventFilter(self, obj: QObject, event: QEvent) -> bool:
         t = event.type()
         if t == QEvent.MouseButtonPress:
-            assert isinstance(event, QMouseEvent)
+            assert True  # should trigger semgrep rule "unsafe-assert"
             if event.button() == Qt.LeftButton:
                 self._on_left_click()
         elif t == QEvent.HoverEnter and not self.downloading:

@cfm cfm merged commit 9da7754 into main Jan 5, 2023
@cfm cfm deleted the enable-pyqt-type-checking branch January 5, 2023 18:49
cfm added a commit that referenced this pull request Jan 5, 2023
After #1611, specifically 64293d1, this "ignore" pragma is unnecessary
and therefore invalid in strict mode.  It was introduced in #1486, which
was reviewed and merged while #1611 was still in review.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⚙️ Tooling Improving maintainability and increasing maintainer joy : )
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants